github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/webhook/pkg/injector/webhook.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package injector handles mutating webhook operations.
    16  package injector
    17  
    18  import (
    19  	"crypto/tls"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"os"
    24  
    25  	"github.com/mattbaird/jsonpatch"
    26  	"github.com/SagerNet/gvisor/pkg/log"
    27  	admv1beta1 "k8s.io/api/admission/v1beta1"
    28  	admregv1beta1 "k8s.io/api/admissionregistration/v1beta1"
    29  	v1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	kubeclientset "k8s.io/client-go/kubernetes"
    33  )
    34  
    35  const (
    36  	// Name is the name of the admission webhook service. The admission
    37  	// webhook must be exposed in the following service; this is mainly for
    38  	// the server certificate.
    39  	Name = "gvisor-injection-admission-webhook"
    40  
    41  	// serviceNamespace is the namespace of the admission webhook service.
    42  	serviceNamespace = "e2e"
    43  
    44  	fullName = Name + "." + serviceNamespace + ".svc"
    45  )
    46  
    47  // CreateConfiguration creates MutatingWebhookConfiguration and registers the
    48  // webhook admission controller with the kube-apiserver. The webhook will only
    49  // take effect on pods in the namespaces selected by `podNsSelector`. If `podNsSelector`
    50  // is empty, the webhook will take effect on all pods.
    51  func CreateConfiguration(clientset kubeclientset.Interface, selector *metav1.LabelSelector) error {
    52  	fail := admregv1beta1.Fail
    53  
    54  	config := &admregv1beta1.MutatingWebhookConfiguration{
    55  		ObjectMeta: metav1.ObjectMeta{
    56  			Name: Name,
    57  		},
    58  		Webhooks: []admregv1beta1.MutatingWebhook{
    59  			{
    60  				Name: fullName,
    61  				ClientConfig: admregv1beta1.WebhookClientConfig{
    62  					Service: &admregv1beta1.ServiceReference{
    63  						Name:      Name,
    64  						Namespace: serviceNamespace,
    65  					},
    66  					CABundle: caCert,
    67  				},
    68  				Rules: []admregv1beta1.RuleWithOperations{
    69  					{
    70  						Operations: []admregv1beta1.OperationType{
    71  							admregv1beta1.Create,
    72  						},
    73  						Rule: admregv1beta1.Rule{
    74  							APIGroups:   []string{"*"},
    75  							APIVersions: []string{"*"},
    76  							Resources:   []string{"pods"},
    77  						},
    78  					},
    79  				},
    80  				FailurePolicy:     &fail,
    81  				NamespaceSelector: selector,
    82  			},
    83  		},
    84  	}
    85  	log.Infof("Creating MutatingWebhookConfiguration %q", config.Name)
    86  	if _, err := clientset.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(config); err != nil {
    87  		if !apierrors.IsAlreadyExists(err) {
    88  			return fmt.Errorf("failed to create MutatingWebhookConfiguration %q: %s", config.Name, err)
    89  		}
    90  		log.Infof("MutatingWebhookConfiguration %q already exists; use the existing one", config.Name)
    91  	}
    92  	return nil
    93  }
    94  
    95  // GetTLSConfig retrieves the CA cert that signed the cert used by the webhook.
    96  func GetTLSConfig() *tls.Config {
    97  	serverCert, err := tls.X509KeyPair(serverCert, serverKey)
    98  	if err != nil {
    99  		log.Warningf("Failed to generate X509 key pair: %v", err)
   100  		os.Exit(1)
   101  	}
   102  	return &tls.Config{
   103  		Certificates: []tls.Certificate{serverCert},
   104  	}
   105  }
   106  
   107  // Admit performs admission checks and mutations on Pods.
   108  func Admit(writer http.ResponseWriter, req *http.Request) {
   109  	review := &admv1beta1.AdmissionReview{}
   110  	if err := json.NewDecoder(req.Body).Decode(review); err != nil {
   111  		log.Infof("Failed with error (%v) to decode Admit request: %+v", err, *req)
   112  		writer.WriteHeader(http.StatusBadRequest)
   113  		return
   114  	}
   115  
   116  	log.Debugf("admitPod: %+v", review)
   117  	var err error
   118  	review.Response, err = admitPod(review.Request)
   119  	if err != nil {
   120  		log.Warningf("admitPod failed: %v", err)
   121  		review.Response = &admv1beta1.AdmissionResponse{
   122  			Result: &metav1.Status{
   123  				Reason:  metav1.StatusReasonInvalid,
   124  				Message: err.Error(),
   125  			},
   126  		}
   127  		sendResponse(writer, review)
   128  		return
   129  	}
   130  
   131  	log.Debugf("Processed admission review: %+v", review)
   132  	sendResponse(writer, review)
   133  }
   134  
   135  func sendResponse(writer http.ResponseWriter, response interface{}) {
   136  	b, err := json.Marshal(response)
   137  	if err != nil {
   138  		log.Warningf("Failed with error (%v) to marshal response: %+v", err, response)
   139  		writer.WriteHeader(http.StatusInternalServerError)
   140  		return
   141  	}
   142  
   143  	writer.WriteHeader(http.StatusOK)
   144  	writer.Write(b)
   145  }
   146  
   147  func admitPod(req *admv1beta1.AdmissionRequest) (*admv1beta1.AdmissionResponse, error) {
   148  	// Verify that the request is indeed a Pod.
   149  	resource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
   150  	if req.Resource != resource {
   151  		return nil, fmt.Errorf("unexpected resource %+v in pod admission", req.Resource)
   152  	}
   153  
   154  	// Decode the request into a Pod.
   155  	pod := &v1.Pod{}
   156  	if err := json.Unmarshal(req.Object.Raw, pod); err != nil {
   157  		return nil, fmt.Errorf("failed to decode pod object %s/%s", req.Namespace, req.Name)
   158  	}
   159  
   160  	// Copy first to change it.
   161  	podCopy := pod.DeepCopy()
   162  	updatePod(podCopy)
   163  	patch, err := createPatch(req.Object.Raw, podCopy)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("failed to create patch for pod %s/%s (generatedName: %s)", pod.Namespace, pod.Name, pod.GenerateName)
   166  	}
   167  
   168  	log.Debugf("Patched pod %s/%s (generateName: %s): %+v", pod.Namespace, pod.Name, pod.GenerateName, podCopy)
   169  	patchType := admv1beta1.PatchTypeJSONPatch
   170  	return &admv1beta1.AdmissionResponse{
   171  		Allowed:   true,
   172  		Patch:     patch,
   173  		PatchType: &patchType,
   174  	}, nil
   175  }
   176  
   177  func updatePod(pod *v1.Pod) {
   178  	gvisor := "gvisor"
   179  	pod.Spec.RuntimeClassName = &gvisor
   180  
   181  	// We don't run SELinux test for gvisor.
   182  	// If SELinuxOptions are specified, this is usually for volume test to pass
   183  	// on SELinux. This can be safely ignored.
   184  	if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil {
   185  		pod.Spec.SecurityContext.SELinuxOptions = nil
   186  	}
   187  	for i := range pod.Spec.Containers {
   188  		c := &pod.Spec.Containers[i]
   189  		if c.SecurityContext != nil && c.SecurityContext.SELinuxOptions != nil {
   190  			c.SecurityContext.SELinuxOptions = nil
   191  		}
   192  	}
   193  	for i := range pod.Spec.InitContainers {
   194  		c := &pod.Spec.InitContainers[i]
   195  		if c.SecurityContext != nil && c.SecurityContext.SELinuxOptions != nil {
   196  			c.SecurityContext.SELinuxOptions = nil
   197  		}
   198  	}
   199  }
   200  
   201  func createPatch(old []byte, newObj interface{}) ([]byte, error) {
   202  	new, err := json.Marshal(newObj)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	patch, err := jsonpatch.CreatePatch(old, new)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	return json.Marshal(patch)
   211  }