diff options
author | gVisor bot <gvisor-bot@google.com> | 2020-10-30 19:05:52 +0000 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-10-30 19:05:52 +0000 |
commit | 0cd6b8317400d88d1b59696596238ec995fb15a8 (patch) | |
tree | 05841fbdb0280f80d26cda50c57db9958b711841 /webhook | |
parent | 4be8cda9f2f9c8a1e6150c47c832494a062cfb1d (diff) | |
parent | d66aebb15a5804f84240dccac55cd56b1dde15e5 (diff) |
Merge release-20201019.0-108-gd66aebb15 (automated)
Diffstat (limited to 'webhook')
-rw-r--r-- | webhook/main.go | 24 | ||||
-rw-r--r-- | webhook/pkg/cli/cli.go | 115 | ||||
-rw-r--r-- | webhook/pkg/cli/cli_state_autogen.go | 3 | ||||
-rw-r--r-- | webhook/pkg/injector/certs.go | 97 | ||||
-rw-r--r-- | webhook/pkg/injector/injector_state_autogen.go | 3 | ||||
-rw-r--r-- | webhook/pkg/injector/webhook.go | 211 |
6 files changed, 453 insertions, 0 deletions
diff --git a/webhook/main.go b/webhook/main.go new file mode 100644 index 000000000..220016543 --- /dev/null +++ b/webhook/main.go @@ -0,0 +1,24 @@ +// 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. + +// Binary main serves a mutating Kubernetes webhook. +package main + +import ( + "gvisor.dev/gvisor/webhook/pkg/cli" +) + +func main() { + cli.Main() +} diff --git a/webhook/pkg/cli/cli.go b/webhook/pkg/cli/cli.go new file mode 100644 index 000000000..a07d341a2 --- /dev/null +++ b/webhook/pkg/cli/cli.go @@ -0,0 +1,115 @@ +// 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 cli provides a CLI interface for a mutating Kubernetes webhook. +package cli + +import ( + "flag" + "fmt" + "net" + "net/http" + "os" + "strconv" + "strings" + + "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/webhook/pkg/injector" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8snet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +var ( + address = flag.String("address", "", "The ip address the admission webhook serves on. If unspecified, a public address is selected automatically.") + port = flag.Int("port", 0, "The port the admission webhook serves on.") + podLabels = flag.String("pod-namespace-labels", "", "A comma-separated namespace label selector, the admission webhook will only take effect on pods in selected namespaces, e.g. `label1,label2`.") +) + +// Main runs the webhook. +func Main() { + flag.Parse() + + if err := run(); err != nil { + log.Warningf("%v", err) + os.Exit(1) + } +} + +func run() error { + log.Infof("Starting %s\n", injector.Name) + + // Create client config. + cfg, err := rest.InClusterConfig() + if err != nil { + return fmt.Errorf("create in cluster config: %w", err) + } + + // Create clientset. + clientset, err := kubernetes.NewForConfig(cfg) + if err != nil { + return fmt.Errorf("create kubernetes client: %w", err) + } + + if err := injector.CreateConfiguration(clientset, parsePodLabels()); err != nil { + return fmt.Errorf("create webhook configuration: %w", err) + } + + if err := startWebhookHTTPS(clientset); err != nil { + return fmt.Errorf("start webhook https server: %w", err) + } + + return nil +} + +func parsePodLabels() *metav1.LabelSelector { + rv := &metav1.LabelSelector{} + for _, s := range strings.Split(*podLabels, ",") { + req := metav1.LabelSelectorRequirement{ + Key: strings.TrimSpace(s), + Operator: "Exists", + } + rv.MatchExpressions = append(rv.MatchExpressions, req) + } + return rv +} + +func startWebhookHTTPS(clientset kubernetes.Interface) error { + log.Infof("Starting HTTPS handler") + defer log.Infof("Stopping HTTPS handler") + + if *address == "" { + ip, err := k8snet.ChooseHostInterface() + if err != nil { + return fmt.Errorf("select ip address: %w", err) + } + *address = ip.String() + } + mux := http.NewServeMux() + mux.Handle("/", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + injector.Admit(w, r) + })) + server := &http.Server{ + // Listen on all addresses. + Addr: net.JoinHostPort(*address, strconv.Itoa(*port)), + TLSConfig: injector.GetTLSConfig(), + Handler: mux, + } + if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed { + return fmt.Errorf("start HTTPS handler: %w", err) + } + return nil +} diff --git a/webhook/pkg/cli/cli_state_autogen.go b/webhook/pkg/cli/cli_state_autogen.go new file mode 100644 index 000000000..e81991e0b --- /dev/null +++ b/webhook/pkg/cli/cli_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package cli diff --git a/webhook/pkg/injector/certs.go b/webhook/pkg/injector/certs.go new file mode 100644 index 000000000..d237d5354 --- /dev/null +++ b/webhook/pkg/injector/certs.go @@ -0,0 +1,97 @@ +package injector + +// This file was generated using openssl by the gencerts.sh script. + +var caKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAzSQsvx5GdE42kwGrZO1WgEGTsR42S+9/BHi0cyElEpgyGwHt +smJdmU1eDed73+Hy23Yy+Pz/WgaroinE65WrV3CeNKCYpvz4EqXF5R4VfGuXaBu7 +79+B98yH6L2UK18MNsTUcrQEvT1lJYHH6s/Zoc0kJFR9j956MPbjc3evKpREEOEO +NYM0SJaUSJgoxoGDxB1ym035m9kDtv5sfogW+jevwVncYPcGcBBl4W9ccdRYG+UW +AR9ZqlW2EaluzxkEMunvVH+gDlH0ZBkpOnIyEIGo998C/xT5bXnb6BmcCu+f++gM +h6lxpkj5FxBYClMfDIZ92lGJWOy3yGg8J3LxZwIDAQABAoIBAB2NRYjuspWUotYA +mpE6g4iMadtND+NWiAS74rrnHnEUTbuIRpa5BuTLuW3lV/oDkbm9yFAIGjz80wLU +Y5LQh9Nb3N6V+AeuT4pYKS79a3j2AuSzghpnJ1DsPPPxQ4QP+DF3n2c6uagNTSHf +FU6lTKO7aqZ1KXVtRksBdfivWCOY4CAd76j/CExzNWxmWhJg2ds3aRsGeZrQxeWI ++OgHqo40qRNAtEqs1x1lLa3oLGM4KCVDAKeACMdFHhYfPLsdc3WciEnIS8eZinkW +q9f+CyrwFJzUTc++NuNSDcQ+hmXIZlUv7rNMNVCOlEdFyxqMr3wa2ZkphRNaxVtf +K9c++bkCgYEA9EujEEwJSDuDi6U2ceBVCoH8w2PopQqC28T5p+c9KQarsyNdFkqK +bn0CzQCKJR5tjl7Ai/yv/nddSYDoDszYYifo5MiUPlGBrcRKqo0K7MN7NuYWWyVQ +Ody9cLdYkjoc2oYkFgYo32EbQSUf7F/aotPBnXhBFMMioBx4sR5xzT0CgYEA1vhM +quKmw3kJHOCPm9FZ1P04IX9fWQiwnz8Vz9Edd7P0WQewzsgOWEZ0Cd4bP/cOu7Vp +JPF2XyZWV+yBoT1jFOBnyRuQHzmzO6AS6OYh9V004UYQtkZkxLrgILjy3BcV9gka ++wA3ek5mnKS9FIib1+D1h73EQO7XftfgRWrsq3MCgYASOlqOar3+j8I+9zLayFxQ +DmbnxVqkheZBs67VImHj38WL9kWJ1kICAH3nAfVM07pk9xjy2QXgvNNPGrk18X7r +xAKSn4zAIaDFcHIJy8BW7jcRX5Wnc19LEfdoo6WOM5vXik7C/e6qzDoWYEjDgFt1 +7srxjvl8LRs2SymOPbFMGQKBgFxxpM6r71kKOMABVeCFE+ODDVtiYgdwtDuXLnMT +E2ABtCeBJiiWYYzWp1oC+Kb2QJC6P8ASUnwyiVkALPLA6lX09szGHKFA9/HBMcCU +DrBsZ6wkrFUmSnlLf8yynEXHa7tFSSP9gN4Izxm3wlQNNy+L3yqDkdz2mRdEEH7p +r2M/AoGAUsuARMasHYJJESeftpMKXrLb7bDmMC7xgCEpavUpjHRfF1r2aaHlpzGt +H60/NFiSmSagMu+hZnplWIlrVMLwhq8+w+WRf00C3ZtiD2Ute/Gc+UZOVeO+4dqX +nyi/78KzitotjV0rTmaT6GKvP1QFuMuVEQ98UWgPRQFjb7gwvaE= +-----END RSA PRIVATE KEY-----`) + +var caCert = []byte(`-----BEGIN CERTIFICATE----- +MIICqzCCAZMCFCdriGGzKjFvuwFvDkgT70JT+SuoMA0GCSqGSIb3DQEBCwUAMBEx +DzANBgNVBAMMBmUyZV9jYTAgFw0yMDEwMzAxOTA1NDFaGA8yMjk0MDgxNTE5MDU0 +MVowETEPMA0GA1UEAwwGZTJlX2NhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAzSQsvx5GdE42kwGrZO1WgEGTsR42S+9/BHi0cyElEpgyGwHtsmJdmU1e +Ded73+Hy23Yy+Pz/WgaroinE65WrV3CeNKCYpvz4EqXF5R4VfGuXaBu779+B98yH +6L2UK18MNsTUcrQEvT1lJYHH6s/Zoc0kJFR9j956MPbjc3evKpREEOEONYM0SJaU +SJgoxoGDxB1ym035m9kDtv5sfogW+jevwVncYPcGcBBl4W9ccdRYG+UWAR9ZqlW2 +EaluzxkEMunvVH+gDlH0ZBkpOnIyEIGo998C/xT5bXnb6BmcCu+f++gMh6lxpkj5 +FxBYClMfDIZ92lGJWOy3yGg8J3LxZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBf +SSBuUjYn/YrffCPsAmQgk9kIZgGg2ByE3A4veIS+rm8yiBhchpRWgRdJoDAqixKJ +6J9Tw9vHZZlfE8lNDrgGCKoIBS1zxZDqY2PVsvE2X+L+q3+US+GuvCBLNdI8Etcx +ElJ10F3UZtOKwCoAPvq7+jM/jGZs7Tp2qQPamS1l0ToghxLYagLNeXC3Id70CPVF +rUS5Kk9HNiLUTmoRnePH9fE0kQl7DUTPictNMSCsThm9B9KBZGDFURUmLeSnmwzC +0DwW4HtPfNShzGswxjhXd/WSvS4PwGevUJtIeEHjpFlU6gN+YmYhr3CxgqQJhC2F +uA1eJQ0tw1wePtiULOdW +-----END CERTIFICATE-----`) + +var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyyHmOyiRpsoGBSKgA6THeRDlqxJj3hNpkpQQZAw98+ZRbpJ+ +AigPJ3ju9/c28KwhxKb7bmSAk0JqlK/VCkO98ZqaUadYvkrjg5yncKqKs48Rx8ld +KOJbTeZPCgwxuzpJguFpTU7gzeY2Ws623/j9VWwCjKOt27ggN1JXw0cgoRDS9Jgj +9TluE1UNfPLty/F32HyWb3NCgjSRW//3Sj/vCAVnbwJop639+lXskjljA/Zcx6ju +JvVF/mhLl58H22NTWz28+qdCxeCypCDLIXOSDuQIXTvmSU5MkHMAxxdZEagAP7ZW +mk9RtCAAmFsSdY1Gs929kSsqNSw380kVoJFM7wIDAQABAoIBAGnK2fFti43mIYSD +J/s90nWHC1YxWbnLOmyQmIjCp3FjPn3r5ZyR6HKSVULnicGGpH+ax9ASn7QSHxgZ +C0cxSLeP4VctZNaWgi+FYt8Wsu4ZURVQFii42vyTOg7tkukDzk1PwFv2/LW/dAeP +KUc0khvvCNTeWinYl064n+SgIqo+/w8kFNRf1FS1JbTbXcxy+dvRBbGhKZ+wcpEO +qSBCFAbbA+M8e1eyu51TAvz3yxhil2csFaRJaLloXmsCB82aqditdo2mJolD8aIf +pkz/HLxEDRDtte231rC7UrbeFP68wyfI688PudndAPnW8qm9SSonBp4yA6BjOEgA +h/xewkECgYEA72o76NqLHaAemcZRQUh0m9lJtMdC6beaaAFyP6rc9g1T4pRbiXfB +X4QVDcmH2qG8xcYTuPMXZJYMBwlGVKy7XQ9BCbGcKD/y818M7odQAgLUoxr9gtFO +5a0RnaCpxEpW63RAjXghCX1KjD7Yt1P1hhHvmtsrqj2IGwZVxun7omcCgYEA2TQ7 +vWVZUM5YXX9cgFLCkJU+NekPWeUjP/5KcDKrF9+cDCwpiZ9c8tMlt4M4TCuFKha1 +H3qd2bTLks3D/jfJt7B3WdQX8VX/i81kroP1G54Ci4hs9X0dVwsWPQ/r076xdfbm +tpAby2W79lLKHXGTh9CFRpVKwleIkSA/D352PDkCgYBIQzNj/BrDTWIPHgnGf50a +sUIK+53Zt142iEE4sFTTO4CXQhpC6s+GCfLk33BO8ERvGXM4fr8P0C4/LXB5/Ezt +ML57s40jpPGqvYTEtjjS8pHFzU65Xn3G2y8W+bhkE+AaX1Ngn+Kw341RuWJmK0RP +PDiq7/5E+x+KsKXRTSxzfwKBgF6GXc1B0wnYkxo1eCMcYTIc0RMTFywvDRUnbGvB +kTX1iWq+uWD8Kq4+d7aSc6iqc+xqL27ApPt+s+1ygO4chzvan0ZHiBfuLiVAQKW2 +JbBcJo1k1M5NIbykxYvTZvUikwZcafFfa8krwA4l33HK4MKFW8ro470J7RQDbY4n +ofr5AoGBAKCpQC0Lue7PeH5aTBKFO4G3CpSutvjdH1A3kJ8NPsO1pfC0Ye4ep45n +4X4NM1xLjS4h5YltrL2dTCBQcwZTHmOmmNAt5d349XclrYYwnBlOi8FV9QgsPglY +hwbWUeiHC7gUdYXfkcXWKb7C84uXNJVy2u0YP0HQh2PA84XFzeET +-----END RSA PRIVATE KEY-----`) + +var serverCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDDzCCAfegAwIBAgIUSwGRpMB/h++K/U2elCgWnjYl89MwDQYJKoZIhvcNAQEL +BQAwETEPMA0GA1UEAwwGZTJlX2NhMCAXDTIwMTAzMDE5MDU0MVoYDzIyOTQwODE1 +MTkwNTQxWjA1MTMwMQYDVQQDDCpndmlzb3ItaW5qZWN0aW9uLWFkbWlzc2lvbi13 +ZWJob29rLmUyZS5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL +IeY7KJGmygYFIqADpMd5EOWrEmPeE2mSlBBkDD3z5lFukn4CKA8neO739zbwrCHE +pvtuZICTQmqUr9UKQ73xmppRp1i+SuODnKdwqoqzjxHHyV0o4ltN5k8KDDG7OkmC +4WlNTuDN5jZazrbf+P1VbAKMo63buCA3UlfDRyChENL0mCP1OW4TVQ188u3L8XfY +fJZvc0KCNJFb//dKP+8IBWdvAminrf36VeySOWMD9lzHqO4m9UX+aEuXnwfbY1Nb +Pbz6p0LF4LKkIMshc5IO5AhdO+ZJTkyQcwDHF1kRqAA/tlaaT1G0IACYWxJ1jUaz +3b2RKyo1LDfzSRWgkUzvAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXg +MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOC +AQEAlvnL1PjLGvv0IgMlWJVPj4c+g3jgg0j6fiK6+7oKSf2Y6wk/7DFRKL4yd23b +BTOzBkAJ0i5J0BQxOSEX0I8qX5b7FxCxwkHVZue3h13//zeFbPqaywwk9uVEkhbR +BKevOaSHjqcC7ci3W0ksl+icBEEmMYvgYqBIB5cZt/Dueam84a1voIZaa32rIOQe +Sivy4NvBGryJPNHs2VmjNdA6g3Zre+tsymwlBLxN03RQ0VWZf+eE3yjuBnKBRyua +rSf8GvwoJrWLl0Q71p8JGG8cPTjHmTwaR8D63yN2QTmINAfXS15zG/PuHsFQ9rpW +JDNmoyI0GR9zyL1M1ZtIZpRuNQ== +-----END CERTIFICATE-----`) diff --git a/webhook/pkg/injector/injector_state_autogen.go b/webhook/pkg/injector/injector_state_autogen.go new file mode 100644 index 000000000..2c994b7c9 --- /dev/null +++ b/webhook/pkg/injector/injector_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package injector 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) +} |