mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-10-05 15:26:57 +08:00
154 lines
4.7 KiB
Go
154 lines
4.7 KiB
Go
package webhook
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
|
|
v1 "k8s.io/api/admission/v1"
|
|
"k8s.io/api/admission/v1beta1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/client-go/kubernetes"
|
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
|
"k8s.io/utils/ptr"
|
|
|
|
plog "github.com/wencaiwulue/kubevpn/v2/pkg/log"
|
|
)
|
|
|
|
// admissionReviewHandler is a handler to handle business logic, holding an util.Factory
|
|
type admissionReviewHandler struct {
|
|
sync.Mutex
|
|
f cmdutil.Factory
|
|
ns string
|
|
clientset *kubernetes.Clientset
|
|
}
|
|
|
|
// admitv1beta1Func handles a v1beta1 admission
|
|
type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
|
|
|
// admitv1beta1Func handles a v1 admission
|
|
type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse
|
|
|
|
// admitHandler is a handler, for both validators and mutators, that supports multiple admission review versions
|
|
type admitHandler struct {
|
|
v1beta1 admitv1beta1Func
|
|
v1 admitv1Func
|
|
}
|
|
|
|
func newDelegateToV1AdmitHandler(f admitv1Func) admitHandler {
|
|
return admitHandler{
|
|
v1beta1: delegateV1beta1AdmitToV1(f),
|
|
v1: f,
|
|
}
|
|
}
|
|
|
|
func delegateV1beta1AdmitToV1(f admitv1Func) admitv1beta1Func {
|
|
return func(review v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|
in := v1.AdmissionReview{Request: convertAdmissionRequestToV1(review.Request)}
|
|
out := f(in)
|
|
return convertAdmissionResponseToV1beta1(out)
|
|
}
|
|
}
|
|
|
|
// serve handles the http portion of a request prior to handing to an admit
|
|
// function
|
|
func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) {
|
|
var body []byte
|
|
if r.Body != nil {
|
|
if data, err := io.ReadAll(r.Body); err == nil {
|
|
body = data
|
|
}
|
|
}
|
|
|
|
// verify the content type is accurate
|
|
contentType := r.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
plog.G(context.Background()).Errorf("ContentType=%s, expect application/json", contentType)
|
|
return
|
|
}
|
|
|
|
deserializer := codecs.UniversalDeserializer()
|
|
obj, gvk, err := deserializer.Decode(body, nil, nil)
|
|
if err != nil {
|
|
msg := fmt.Sprintf("Request: %s could not be decoded: %v", string(body), err)
|
|
plog.G(context.Background()).Error(msg)
|
|
http.Error(w, msg, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var responseObj runtime.Object
|
|
switch *gvk {
|
|
case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"):
|
|
requestedAdmissionReview, ok := obj.(*v1beta1.AdmissionReview)
|
|
if !ok {
|
|
plog.G(context.Background()).Errorf("Expected v1beta1.AdmissionReview but got: %T", obj)
|
|
return
|
|
}
|
|
if ptr.Deref(requestedAdmissionReview.Request.DryRun, false) {
|
|
plog.G(context.Background()).Info("Ignore dryrun")
|
|
responseObj = &v1beta1.AdmissionReview{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: gvk.GroupVersion().String(),
|
|
Kind: gvk.Kind,
|
|
},
|
|
Response: &v1beta1.AdmissionResponse{
|
|
Allowed: true,
|
|
UID: requestedAdmissionReview.Request.UID,
|
|
},
|
|
}
|
|
} else {
|
|
responseAdmissionReview := &v1beta1.AdmissionReview{}
|
|
responseAdmissionReview.SetGroupVersionKind(*gvk)
|
|
responseAdmissionReview.Response = admit.v1beta1(*requestedAdmissionReview)
|
|
responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
|
|
responseObj = responseAdmissionReview
|
|
}
|
|
case v1.SchemeGroupVersion.WithKind("AdmissionReview"):
|
|
requestedAdmissionReview, ok := obj.(*v1.AdmissionReview)
|
|
if !ok {
|
|
plog.G(context.Background()).Errorf("Expected v1.AdmissionReview but got: %T", obj)
|
|
return
|
|
}
|
|
if ptr.Deref(requestedAdmissionReview.Request.DryRun, false) {
|
|
plog.G(context.Background()).Info("Ignore dry-run")
|
|
responseObj = &v1.AdmissionReview{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: gvk.GroupVersion().String(),
|
|
Kind: gvk.Kind,
|
|
},
|
|
Response: &v1.AdmissionResponse{
|
|
Allowed: true,
|
|
UID: requestedAdmissionReview.Request.UID,
|
|
},
|
|
}
|
|
} else {
|
|
responseAdmissionReview := &v1.AdmissionReview{}
|
|
responseAdmissionReview.SetGroupVersionKind(*gvk)
|
|
responseAdmissionReview.Response = admit.v1(*requestedAdmissionReview)
|
|
responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
|
|
responseObj = responseAdmissionReview
|
|
}
|
|
default:
|
|
msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)
|
|
plog.G(context.Background()).Error(msg)
|
|
http.Error(w, msg, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
respBytes, err := json.Marshal(responseObj)
|
|
if err != nil {
|
|
plog.G(context.Background()).Errorf("Unable to encode response: %v", err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
plog.G(context.Background()).Infof("Sending response: %v", string(respBytes))
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if _, err = w.Write(respBytes); err != nil {
|
|
plog.G(context.Background()).Errorf("Unable to write response: %v", err)
|
|
}
|
|
}
|