github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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 "github.com/nicocha30/gvisor-ligolo/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 }