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 }