summaryrefslogtreecommitdiffhomepage
path: root/webhook/pkg/injector/webhook.go
diff options
context:
space:
mode:
Diffstat (limited to 'webhook/pkg/injector/webhook.go')
-rw-r--r--webhook/pkg/injector/webhook.go211
1 files changed, 211 insertions, 0 deletions
diff --git a/webhook/pkg/injector/webhook.go b/webhook/pkg/injector/webhook.go
new file mode 100644
index 000000000..614b5add7
--- /dev/null
+++ b/webhook/pkg/injector/webhook.go
@@ -0,0 +1,211 @@
+// Copyright 2020 The gVisor 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 injector handles mutating webhook operations.
+package injector
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+
+ "github.com/mattbaird/jsonpatch"
+ "gvisor.dev/gvisor/pkg/log"
+ admv1beta1 "k8s.io/api/admission/v1beta1"
+ admregv1beta1 "k8s.io/api/admissionregistration/v1beta1"
+ v1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ kubeclientset "k8s.io/client-go/kubernetes"
+)
+
+const (
+ // Name is the name of the admission webhook service. The admission
+ // webhook must be exposed in the following service; this is mainly for
+ // the server certificate.
+ Name = "gvisor-injection-admission-webhook"
+
+ // serviceNamespace is the namespace of the admission webhook service.
+ serviceNamespace = "e2e"
+
+ fullName = Name + "." + serviceNamespace + ".svc"
+)
+
+// CreateConfiguration creates MutatingWebhookConfiguration and registers the
+// webhook admission controller with the kube-apiserver. The webhook will only
+// take effect on pods in the namespaces selected by `podNsSelector`. If `podNsSelector`
+// is empty, the webhook will take effect on all pods.
+func CreateConfiguration(clientset kubeclientset.Interface, selector *metav1.LabelSelector) error {
+ fail := admregv1beta1.Fail
+
+ config := &admregv1beta1.MutatingWebhookConfiguration{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: Name,
+ },
+ Webhooks: []admregv1beta1.MutatingWebhook{
+ {
+ Name: fullName,
+ ClientConfig: admregv1beta1.WebhookClientConfig{
+ Service: &admregv1beta1.ServiceReference{
+ Name: Name,
+ Namespace: serviceNamespace,
+ },
+ CABundle: caCert,
+ },
+ Rules: []admregv1beta1.RuleWithOperations{
+ {
+ Operations: []admregv1beta1.OperationType{
+ admregv1beta1.Create,
+ },
+ Rule: admregv1beta1.Rule{
+ APIGroups: []string{"*"},
+ APIVersions: []string{"*"},
+ Resources: []string{"pods"},
+ },
+ },
+ },
+ FailurePolicy: &fail,
+ NamespaceSelector: selector,
+ },
+ },
+ }
+ log.Infof("Creating MutatingWebhookConfiguration %q", config.Name)
+ if _, err := clientset.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(config); err != nil {
+ if !apierrors.IsAlreadyExists(err) {
+ return fmt.Errorf("failed to create MutatingWebhookConfiguration %q: %s", config.Name, err)
+ }
+ log.Infof("MutatingWebhookConfiguration %q already exists; use the existing one", config.Name)
+ }
+ return nil
+}
+
+// GetTLSConfig retrieves the CA cert that signed the cert used by the webhook.
+func GetTLSConfig() *tls.Config {
+ serverCert, err := tls.X509KeyPair(serverCert, serverKey)
+ if err != nil {
+ log.Warningf("Failed to generate X509 key pair: %v", err)
+ os.Exit(1)
+ }
+ return &tls.Config{
+ Certificates: []tls.Certificate{serverCert},
+ }
+}
+
+// Admit performs admission checks and mutations on Pods.
+func Admit(writer http.ResponseWriter, req *http.Request) {
+ review := &admv1beta1.AdmissionReview{}
+ if err := json.NewDecoder(req.Body).Decode(review); err != nil {
+ log.Infof("Failed with error (%v) to decode Admit request: %+v", err, *req)
+ writer.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ log.Debugf("admitPod: %+v", review)
+ var err error
+ review.Response, err = admitPod(review.Request)
+ if err != nil {
+ log.Warningf("admitPod failed: %v", err)
+ review.Response = &admv1beta1.AdmissionResponse{
+ Result: &metav1.Status{
+ Reason: metav1.StatusReasonInvalid,
+ Message: err.Error(),
+ },
+ }
+ sendResponse(writer, review)
+ return
+ }
+
+ log.Debugf("Processed admission review: %+v", review)
+ sendResponse(writer, review)
+}
+
+func sendResponse(writer http.ResponseWriter, response interface{}) {
+ b, err := json.Marshal(response)
+ if err != nil {
+ log.Warningf("Failed with error (%v) to marshal response: %+v", err, response)
+ writer.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ writer.WriteHeader(http.StatusOK)
+ writer.Write(b)
+}
+
+func admitPod(req *admv1beta1.AdmissionRequest) (*admv1beta1.AdmissionResponse, error) {
+ // Verify that the request is indeed a Pod.
+ resource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
+ if req.Resource != resource {
+ return nil, fmt.Errorf("unexpected resource %+v in pod admission", req.Resource)
+ }
+
+ // Decode the request into a Pod.
+ pod := &v1.Pod{}
+ if err := json.Unmarshal(req.Object.Raw, pod); err != nil {
+ return nil, fmt.Errorf("failed to decode pod object %s/%s", req.Namespace, req.Name)
+ }
+
+ // Copy first to change it.
+ podCopy := pod.DeepCopy()
+ updatePod(podCopy)
+ patch, err := createPatch(req.Object.Raw, podCopy)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create patch for pod %s/%s (generatedName: %s)", pod.Namespace, pod.Name, pod.GenerateName)
+ }
+
+ log.Debugf("Patched pod %s/%s (generateName: %s): %+v", pod.Namespace, pod.Name, pod.GenerateName, podCopy)
+ patchType := admv1beta1.PatchTypeJSONPatch
+ return &admv1beta1.AdmissionResponse{
+ Allowed: true,
+ Patch: patch,
+ PatchType: &patchType,
+ }, nil
+}
+
+func updatePod(pod *v1.Pod) {
+ gvisor := "gvisor"
+ pod.Spec.RuntimeClassName = &gvisor
+
+ // We don't run SELinux test for gvisor.
+ // If SELinuxOptions are specified, this is usually for volume test to pass
+ // on SELinux. This can be safely ignored.
+ if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil {
+ pod.Spec.SecurityContext.SELinuxOptions = nil
+ }
+ for i := range pod.Spec.Containers {
+ c := &pod.Spec.Containers[i]
+ if c.SecurityContext != nil && c.SecurityContext.SELinuxOptions != nil {
+ c.SecurityContext.SELinuxOptions = nil
+ }
+ }
+ for i := range pod.Spec.InitContainers {
+ c := &pod.Spec.InitContainers[i]
+ if c.SecurityContext != nil && c.SecurityContext.SELinuxOptions != nil {
+ c.SecurityContext.SELinuxOptions = nil
+ }
+ }
+}
+
+func createPatch(old []byte, newObj interface{}) ([]byte, error) {
+ new, err := json.Marshal(newObj)
+ if err != nil {
+ return nil, err
+ }
+ patch, err := jsonpatch.CreatePatch(old, new)
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(patch)
+}