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