github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/internal/agent/http/admission.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package http provides a http server for the webhook and proxy. 5 package http 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 13 "github.com/Racer159/jackal/src/config/lang" 14 "github.com/Racer159/jackal/src/internal/agent/operations" 15 "github.com/Racer159/jackal/src/pkg/message" 16 v1 "k8s.io/api/admission/v1" 17 meta "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/runtime" 19 "k8s.io/apimachinery/pkg/runtime/serializer" 20 ) 21 22 // admissionHandler represents the HTTP handler for an admission webhook. 23 type admissionHandler struct { 24 decoder runtime.Decoder 25 } 26 27 // newAdmissionHandler returns an instance of AdmissionHandler. 28 func newAdmissionHandler() *admissionHandler { 29 return &admissionHandler{ 30 decoder: serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer(), 31 } 32 } 33 34 // Serve returns a http.HandlerFunc for an admission webhook. 35 func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { 36 message.Debugf("http.Serve(%#v)", hook) 37 return func(w http.ResponseWriter, r *http.Request) { 38 message.Debugf("http.Serve()(writer, %#v)", r.URL) 39 40 w.Header().Set("Content-Type", "application/json") 41 if r.Method != http.MethodPost { 42 http.Error(w, lang.AgentErrInvalidMethod, http.StatusMethodNotAllowed) 43 return 44 } 45 46 if contentType := r.Header.Get("Content-Type"); contentType != "application/json" { 47 http.Error(w, lang.AgentErrInvalidType, http.StatusBadRequest) 48 return 49 } 50 51 body, err := io.ReadAll(r.Body) 52 if err != nil { 53 http.Error(w, fmt.Sprintf(lang.AgentErrBadRequest, err), http.StatusBadRequest) 54 return 55 } 56 57 var review v1.AdmissionReview 58 if _, _, err := h.decoder.Decode(body, nil, &review); err != nil { 59 http.Error(w, fmt.Sprintf(lang.AgentErrCouldNotDeserializeReq, err), http.StatusBadRequest) 60 return 61 } 62 63 if review.Request == nil { 64 http.Error(w, lang.AgentErrNilReq, http.StatusBadRequest) 65 return 66 } 67 68 result, err := hook.Execute(review.Request) 69 if err != nil { 70 message.WarnErr(err, lang.AgentErrBindHandler) 71 w.WriteHeader(http.StatusInternalServerError) 72 return 73 } 74 75 admissionResponse := v1.AdmissionReview{ 76 TypeMeta: meta.TypeMeta{ 77 APIVersion: v1.SchemeGroupVersion.String(), 78 Kind: "AdmissionReview", 79 }, 80 Response: &v1.AdmissionResponse{ 81 UID: review.Request.UID, 82 Allowed: result.Allowed, 83 Result: &meta.Status{Message: result.Msg}, 84 }, 85 } 86 87 // set the patch operations for mutating admission 88 if len(result.PatchOps) > 0 { 89 jsonPatchType := v1.PatchTypeJSONPatch 90 patchBytes, err := json.Marshal(result.PatchOps) 91 if err != nil { 92 message.WarnErr(err, lang.AgentErrMarshallJSONPatch) 93 http.Error(w, lang.AgentErrMarshallJSONPatch, http.StatusInternalServerError) 94 } 95 admissionResponse.Response.Patch = patchBytes 96 admissionResponse.Response.PatchType = &jsonPatchType 97 } 98 99 jsonResponse, err := json.Marshal(admissionResponse) 100 if err != nil { 101 message.WarnErr(err, lang.AgentErrMarshalResponse) 102 http.Error(w, lang.AgentErrMarshalResponse, http.StatusInternalServerError) 103 return 104 } 105 106 message.Debug("PATCH: ", string(admissionResponse.Response.Patch)) 107 message.Debug("RESPONSE: ", string(jsonResponse)) 108 109 message.Infof(lang.AgentInfoWebhookAllowed, r.URL.Path, review.Request.Operation, result.Allowed) 110 w.WriteHeader(http.StatusOK) 111 w.Write(jsonResponse) 112 } 113 }