123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- /*
- Copyright 2018 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package webhook
- import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "github.com/spf13/cobra"
- v1 "k8s.io/api/admission/v1"
- "k8s.io/api/admission/v1beta1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/klog"
- // TODO: try this library to see if it generates correct json patch
- // https://github.com/mattbaird/jsonpatch
- )
- var (
- certFile string
- keyFile string
- port int
- sidecarImage string
- )
- // CmdWebhook is used by agnhost Cobra.
- var CmdWebhook = &cobra.Command{
- Use: "webhook",
- Short: "Starts a HTTP server, useful for testing MutatingAdmissionWebhook and ValidatingAdmissionWebhook",
- Long: `Starts a HTTP server, useful for testing MutatingAdmissionWebhook and ValidatingAdmissionWebhook.
- After deploying it to Kubernetes cluster, the Administrator needs to create a ValidatingWebhookConfiguration
- in the Kubernetes cluster to register remote webhook admission controllers.`,
- Args: cobra.MaximumNArgs(0),
- Run: main,
- }
- func init() {
- CmdWebhook.Flags().StringVar(&certFile, "tls-cert-file", "",
- "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).")
- CmdWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
- "File containing the default x509 private key matching --tls-cert-file.")
- CmdWebhook.Flags().IntVar(&port, "port", 443,
- "Secure port that the webhook listens on")
- CmdWebhook.Flags().StringVar(&sidecarImage, "sidecar-image", "",
- "Image to be used as the injected sidecar")
- }
- // 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 := ioutil.ReadAll(r.Body); err == nil {
- body = data
- }
- }
- // verify the content type is accurate
- contentType := r.Header.Get("Content-Type")
- if contentType != "application/json" {
- klog.Errorf("contentType=%s, expect application/json", contentType)
- return
- }
- klog.V(2).Info(fmt.Sprintf("handling request: %s", body))
- deserializer := codecs.UniversalDeserializer()
- obj, gvk, err := deserializer.Decode(body, nil, nil)
- if err != nil {
- msg := fmt.Sprintf("Request could not be decoded: %v", err)
- klog.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 {
- klog.Errorf("Expected v1beta1.AdmissionReview but got: %T", obj)
- return
- }
- 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 {
- klog.Errorf("Expected v1.AdmissionReview but got: %T", obj)
- return
- }
- 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)
- klog.Error(msg)
- http.Error(w, msg, http.StatusBadRequest)
- return
- }
- klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj))
- respBytes, err := json.Marshal(responseObj)
- if err != nil {
- klog.Error(err)
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- w.Header().Set("Content-Type", "application/json")
- if _, err := w.Write(respBytes); err != nil {
- klog.Error(err)
- }
- }
- func serveAlwaysAllowDelayFiveSeconds(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(alwaysAllowDelayFiveSeconds))
- }
- func serveAlwaysDeny(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(alwaysDeny))
- }
- func serveAddLabel(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(addLabel))
- }
- func servePods(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(admitPods))
- }
- func serveAttachingPods(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(denySpecificAttachment))
- }
- func serveMutatePods(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(mutatePods))
- }
- func serveMutatePodsSidecar(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(mutatePodsSidecar))
- }
- func serveConfigmaps(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(admitConfigMaps))
- }
- func serveMutateConfigmaps(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(mutateConfigmaps))
- }
- func serveCustomResource(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(admitCustomResource))
- }
- func serveMutateCustomResource(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(mutateCustomResource))
- }
- func serveCRD(w http.ResponseWriter, r *http.Request) {
- serve(w, r, newDelegateToV1AdmitHandler(admitCRD))
- }
- func main(cmd *cobra.Command, args []string) {
- config := Config{
- CertFile: certFile,
- KeyFile: keyFile,
- }
- http.HandleFunc("/always-allow-delay-5s", serveAlwaysAllowDelayFiveSeconds)
- http.HandleFunc("/always-deny", serveAlwaysDeny)
- http.HandleFunc("/add-label", serveAddLabel)
- http.HandleFunc("/pods", servePods)
- http.HandleFunc("/pods/attach", serveAttachingPods)
- http.HandleFunc("/mutating-pods", serveMutatePods)
- http.HandleFunc("/mutating-pods-sidecar", serveMutatePodsSidecar)
- http.HandleFunc("/configmaps", serveConfigmaps)
- http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
- http.HandleFunc("/custom-resource", serveCustomResource)
- http.HandleFunc("/mutating-custom-resource", serveMutateCustomResource)
- http.HandleFunc("/crd", serveCRD)
- http.HandleFunc("/readyz", func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("ok")) })
- server := &http.Server{
- Addr: fmt.Sprintf(":%d", port),
- TLSConfig: configTLS(config),
- }
- err := server.ListenAndServeTLS("", "")
- if err != nil {
- panic(err)
- }
- }
|