diff --git a/cmd/nginx/flags.go b/cmd/nginx/flags.go index c62f6c4ce3..537b4770ba 100644 --- a/cmd/nginx/flags.go +++ b/cmd/nginx/flags.go @@ -159,6 +159,14 @@ Feature backed by OpenResty Lua libraries. Requires that OCSP stapling is not en disableCatchAll = flags.Bool("disable-catch-all", false, `Disable support for catch-all Ingresses`) + + validationWebhook = flags.String("validating-webhook", "", + `The address to start an admission controller on to validate incoming ingresses. +Takes the form ":port". If not provided, no admission controller is started.`) + validationWebhookCert = flags.String("validating-webhook-certificate", "", + `The path of the validating webhook certificate PEM.`) + validationWebhookKey = flags.String("validating-webhook-key", "", + `The path of the validating webhook key PEM.`) ) flags.MarkDeprecated("status-port", `The status port is a unix socket now.`) @@ -255,7 +263,10 @@ Feature backed by OpenResty Lua libraries. Requires that OCSP stapling is not en HTTPS: *httpsPort, SSLProxy: *sslProxyPort, }, - DisableCatchAll: *disableCatchAll, + DisableCatchAll: *disableCatchAll, + ValidationWebhook: *validationWebhook, + ValidationWebhookCertPath: *validationWebhookCert, + ValidationWebhookKeyPath: *validationWebhookKey, } return false, config, nil diff --git a/deploy/validating-webhook.yaml.tpl b/deploy/validating-webhook.yaml.tpl new file mode 100644 index 0000000000..7aad64f8c9 --- /dev/null +++ b/deploy/validating-webhook.yaml.tpl @@ -0,0 +1,25 @@ +--- +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingWebhookConfiguration +metadata: + name: check-ingress +webhooks: +- name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - extensions + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + failurePolicy: Fail + clientConfig: + service: + namespace: ingress-nginx + name: nginx-ingress-webhook + path: /extensions/v1beta1/ingresses + caBundle: +--- \ No newline at end of file diff --git a/deploy/with-validating-webhook.yaml.tpl b/deploy/with-validating-webhook.yaml.tpl new file mode 100644 index 0000000000..15cf37c850 --- /dev/null +++ b/deploy/with-validating-webhook.yaml.tpl @@ -0,0 +1,115 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: ingress-validation-webhook + namespace: ingress-nginx +spec: + ports: + - name: admission + port: 443 + protocol: TCP + targetPort: 8080 + selector: + app.kubernetes.io/name: ingress-nginx +--- +apiVersion: v1 +data: + key.pem: + certificate.pem: +kind: Secret +metadata: + name: nginx-ingress-webhook-certificate + namespace: ingress-nginx +type: Opaque +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-ingress-controller + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + annotations: + prometheus.io/port: "10254" + prometheus.io/scrape: "true" + spec: + serviceAccountName: nginx-ingress-serviceaccount + containers: + - name: nginx-ingress-controller + image: containers.schibsted.io/thibault-jamet/ingress-nginx:0.23.0-schibsted + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-configuration + - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services + - --udp-services-configmap=$(POD_NAMESPACE)/udp-services + - --publish-service=$(POD_NAMESPACE)/ingress-nginx + - --annotations-prefix=nginx.ingress.kubernetes.io + - --validating-webhook=:8080 + - --validating-webhook-certificate=/usr/local/certificates/certificate.pem + - --validating-webhook-key=/usr/local/certificates/key.pem + volumeMounts: + - name: webhook-cert + mountPath: "/usr/local/certificates/" + readOnly: true + securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + # www-data -> 33 + runAsUser: 33 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + - name: https + containerPort: 443 + - name: webhook + containerPort: 8080 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 10 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 10 + volumes: + - name: webhook-cert + secret: + secretName: nginx-ingress-webhook-certificate +--- diff --git a/docs/deploy/validating-webhook.md b/docs/deploy/validating-webhook.md new file mode 100644 index 0000000000..c083fa35eb --- /dev/null +++ b/docs/deploy/validating-webhook.md @@ -0,0 +1,168 @@ +# Validating webhook (admission controller) + +## Overview + +Nginx ingress controller offers the option to validate ingresses before they enter the cluster, ensuring controller will generate a valid configuration. + +This controller is called, when [ValidatingAdmissionWebhook][1] is enabled, by the Kubernetes API server each time a new ingress is to enter the cluster, and rejects objects for which the generated nginx configuration fails to be validated. + +This feature requires some further configuration of the cluster, hence it is an optional feature, this section explains how to enable it for your cluster. + +## Configure the webhook + +### Generate the webhook certificate + + +#### Self signed certificate + +Validating webhook must be served using TLS, you need to generate a certificate. Note that kube API server is checking the hostname of the certificate, the common name of your certificate will need to match the service name. + +!!! example + To run the validating webhook with a service named `ingress-validation-webhook` in the namespace `ingress-nginx`, run + + ```bash + openssl req -x509 -newkey rsa:2048 -keyout certificate.pem -out key.pem -days 365 -nodes -subj "/CN=ingress-validation-webhook.ingress-nginx.svc" + ``` + +##### Using Kubernetes CA + +Kubernetes also provides primitives to sign a certificate request. Here is an example on how to use it + +!!! example + ``` + #!/bin/bash + + SERVICE_NAME=ingress-nginx + NAMESPACE=ingress-nginx + + TEMP_DIRECTORY=$(mktemp -d) + echo "creating certs in directory ${TEMP_DIRECTORY}" + + cat <> ${TEMP_DIRECTORY}/csr.conf + [req] + req_extensions = v3_req + distinguished_name = req_distinguished_name + [req_distinguished_name] + [ v3_req ] + basicConstraints = CA:FALSE + keyUsage = nonRepudiation, digitalSignature, keyEncipherment + extendedKeyUsage = serverAuth + subjectAltName = @alt_names + [alt_names] + DNS.1 = ${SERVICE_NAME} + DNS.2 = ${SERVICE_NAME}.${NAMESPACE} + DNS.3 = ${SERVICE_NAME}.${NAMESPACE}.svc + EOF + + openssl genrsa -out ${TEMP_DIRECTORY}/server-key.pem 2048 + openssl req -new -key ${TEMP_DIRECTORY}/server-key.pem \ + -subj "/CN=${SERVICE_NAME}.${NAMESPACE}.svc" \ + -out ${TEMP_DIRECTORY}/server.csr \ + -config ${TEMP_DIRECTORY}/csr.conf + + cat <&2 + exit 1 + fi + echo ${SERVER_CERT} | openssl base64 -d -A -out ${TEMP_DIRECTORY}/server-cert.pem + + kubectl create secret generic ingress-nginx.svc \ + --from-file=key.pem=${TEMP_DIRECTORY}/server-key.pem \ + --from-file=cert.pem=${TEMP_DIRECTORY}/server-cert.pem \ + -n ${NAMESPACE} + ``` + +#### Using helm + +To generate the certificate using helm, you can use the following snippet + +!!! example + ``` + {{- $cn := printf "%s.%s.svc" ( include "nginx-ingress.validatingWebhook.fullname" . ) .Release.Namespace }} + {{- $ca := genCA (printf "%s-ca" ( include "nginx-ingress.validatingWebhook.fullname" . )) .Values.validatingWebhook.certificateValidity -}} + {{- $cert := genSignedCert $cn nil nil .Values.validatingWebhook.certificateValidity $ca -}} + ``` + +### Ingress controller flags + +To enable the feature in the ingress controller, you _need_ to provide 3 flags to the command line. + +|flag|description|example usage| +|-|-|-| +|`--validating-webhook`|The address to start an admission controller on|`:8080`| +|`--validating-webhook-certificate`|The certificate the webhook is using for its TLS handling|`/usr/local/certificates/validating-webhook.pem`| +|`--validating-webhook-key`|The key the webhook is using for its TLS handling|`/usr/local/certificates/validating-webhook-key.pem`| + +### kube API server flags + +Validating webhook feature requires specific setup on the kube API server side. Depending on your kubernetes version, the flag can, or not, be enabled by default. +To check that your kube API server runs with the required flags, please refer to the [kubernetes][1] documentation. + +### Additional kubernetes objects + +Once both the ingress controller and the kube API server are configured to serve the webhook, add the you can configure the webhook with the following objects: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: ingress-validation-webhook + namespace: ingress-nginx +spec: + ports: + - name: admission + port: 443 + protocol: TCP + targetPort: 8080 + selector: + app: nginx-ingress + component: controller +--- +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingWebhookConfiguration +metadata: + name: check-ingress +webhooks: +- name: validate.nginx.ingress.kubernetes.io + rules: + - apiGroups: + - extensions + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + failurePolicy: Fail + clientConfig: + service: + namespace: ingress-nginx + name: ingress-validation-webhook + path: /extensions/v1beta1/ingress + caBundle: +``` + +[1]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook \ No newline at end of file diff --git a/docs/how-it-works.md b/docs/how-it-works.md index f33ae0361c..b69df31ed3 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -56,6 +56,13 @@ On every endpoint change the controller fetches endpoints from all the services In a relatively big clusters with frequently deploying apps this feature saves significant number of Nginx reloads which can otherwise affect response latency, load balancing quality (after every reload Nginx resets the state of load balancing) and so on. +### Avoiding outage from wrong configuration + +Because the ingress controller works using the [synchronization loop pattern](https://coreos.com/kubernetes/docs/latest/replication-controller.html#the-reconciliation-loop-in-detail), it is applying the configuration for all matching objects. In case some Ingress objects have a broken configuration, for example a syntax error in the `nginx.ingress.kubernetes.io/configuration-snippet` annotation, the generated configuration becomes invalid, does not reload and hence no more ingresses will be taken into account. + +To prevent this situation to happen, the nginx ingress controller exposes optionnally a [validating admission webhook server][8] to ensure the validity of incoming ingress objects. +This webhook appends the incoming ingress objects to the list of ingresses, generates the configuration and calls nginx to ensure the configuration has no syntax errors. + [0]: https://github.com/openresty/lua-nginx-module/pull/1259 [1]: https://coreos.com/kubernetes/docs/latest/replication-controller.html#the-reconciliation-loop-in-detail [2]: https://godoc.org/k8s.io/client-go/informers#NewFilteredSharedInformerFactory @@ -64,3 +71,4 @@ In a relatively big clusters with frequently deploying apps this feature saves s [5]: https://golang.org/pkg/sync/#Mutex [6]: https://github.com/kubernetes/ingress-nginx/blob/master/rootfs/etc/nginx/template/nginx.tmpl [7]: http://nginx.org/en/docs/beginners_guide.html#control +[8]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook diff --git a/docs/user-guide/cli-arguments.md b/docs/user-guide/cli-arguments.md index 5e860a49a7..b8581a2761 100644 --- a/docs/user-guide/cli-arguments.md +++ b/docs/user-guide/cli-arguments.md @@ -44,3 +44,7 @@ They are set in the container spec of the `nginx-ingress-controller` Deployment | `--version` | Show release information about the NGINX Ingress controller and exit. | | `--vmodule moduleSpec` | comma-separated list of pattern=N settings for file-filtered logging | | `--watch-namespace string` | Namespace the controller watches for updates to Kubernetes objects. This includes Ingresses, Services and all configuration resources. All namespaces are watched if this parameter is left empty. | +| `--disable-catch-all` | Disable support for catch-all Ingresses. | +|`--validating-webhook`|The address to start an admission controller on| +|`--validating-webhook-certificate`|The certificate the webhook is using for its TLS handling| +|`--validating-webhook-key`|The key the webhook is using for its TLS handling| diff --git a/go.mod b/go.mod index 1deca6e5ff..a8c4e3fdd7 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,10 @@ require ( github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect github.com/go-openapi/spec v0.19.0 // indirect + github.com/gofortune/gofortune v0.0.1-snapshot // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/google/gofuzz v1.0.0 // indirect + github.com/google/uuid v1.0.0 github.com/googleapis/gnostic v0.2.0 // indirect github.com/gophercloud/gophercloud v0.0.0-20190410012400-2c55d17f707c // indirect github.com/imdario/mergo v0.3.7 @@ -54,7 +56,9 @@ require ( github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.3 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 + github.com/vromero/gofortune v0.0.1-snapshot github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272 + google.golang.org/grpc v1.19.1 gopkg.in/fsnotify/fsnotify.v1 v1.4.7 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/pool.v3 v3.1.1 diff --git a/go.sum b/go.sum index 3c21085821..183c4d444d 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofortune/gofortune v0.0.1-snapshot h1:0unUpPzS0PAdMrOvLAhmeaGtFlUPYv5aXUD/9XN5X9U= +github.com/gofortune/gofortune v0.0.1-snapshot/go.mod h1:gzHWMyrWq6g1heq6667VSJTUxWXv+9mTry2HjUnEVB4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -234,6 +236,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/vromero/gofortune v0.0.1-snapshot h1:+IDjezRGmRO1Mdm1Oh+DguaSkxjRpoevWlpdTSlwPkw= +github.com/vromero/gofortune v0.0.1-snapshot/go.mod h1:t8EOM3RyBWLevtrXkmQtfAMmH5CU3/YcnpG5RZ/GQXQ= github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272 h1:scDk3LAM8x+NPuywVGC0q0/G+5Ed8M0+YXecz4XnWRM= github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272/go.mod h1:KNkcm66cr4ilOiEcjydK+tc2ShPUhqmuoXCljXUBPu8= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= diff --git a/internal/admission/controller/main.go b/internal/admission/controller/main.go new file mode 100644 index 0000000000..396d95a2a0 --- /dev/null +++ b/internal/admission/controller/main.go @@ -0,0 +1,93 @@ +/* +Copyright 2019 The Kubernetes 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 controller + +import ( + "github.com/google/uuid" + "k8s.io/api/admission/v1beta1" + extensions "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/klog" +) + +// Checker must return an error if the ingress provided as argument +// contains invalid instructions +type Checker interface { + CheckIngress(ing *extensions.Ingress) error +} + +// IngressAdmission implements the AdmissionController interface +// to handle Admission Reviews and deny requests that are not validated +type IngressAdmission struct { + Checker Checker +} + +// HandleAdmission populates the admission Response +// with Allowed=false if the Object is an ingress that would prevent nginx to reload the configuration +// with Allowed=true otherwise +func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) error { + if ar.Request == nil { + klog.Infof("rejecting nil request") + ar.Response = &v1beta1.AdmissionResponse{ + UID: types.UID(uuid.New().String()), + Allowed: false, + } + return nil + } + klog.V(3).Infof("handling ingress admission webhook request for {%s} %s in namespace %s", ar.Request.Resource.String(), ar.Request.Name, ar.Request.Namespace) + + ingressResource := v1.GroupVersionResource{Group: extensions.SchemeGroupVersion.Group, Version: extensions.SchemeGroupVersion.Version, Resource: "ingresses"} + + if ar.Request.Resource == ingressResource { + ar.Response = &v1beta1.AdmissionResponse{ + UID: types.UID(uuid.New().String()), + Allowed: false, + } + ingress := extensions.Ingress{} + deserializer := codecs.UniversalDeserializer() + if _, _, err := deserializer.Decode(ar.Request.Object.Raw, nil, &ingress); err != nil { + ar.Response.Result = &v1.Status{Message: err.Error()} + ar.Response.AuditAnnotations = map[string]string{ + parser.GetAnnotationWithPrefix("error"): err.Error(), + } + klog.Errorf("failed to decode ingress %s in namespace %s: %s, refusing it", ar.Request.Name, ar.Request.Namespace, err.Error()) + return err + } + + err := ia.Checker.CheckIngress(&ingress) + if err != nil { + ar.Response.Result = &v1.Status{Message: err.Error()} + ar.Response.AuditAnnotations = map[string]string{ + parser.GetAnnotationWithPrefix("error"): err.Error(), + } + klog.Errorf("failed to generate configuration for ingress %s in namespace %s: %s, refusing it", ar.Request.Name, ar.Request.Namespace, err.Error()) + return err + } + ar.Response.Allowed = true + klog.Infof("successfully validated configuration, accepting ingress %s in namespace %s", ar.Request.Name, ar.Request.Namespace) + return nil + } + + klog.Infof("accepting non ingress %s in namespace %s %s", ar.Request.Name, ar.Request.Namespace, ar.Request.Resource.String()) + ar.Response = &v1beta1.AdmissionResponse{ + UID: types.UID(uuid.New().String()), + Allowed: true, + } + return nil +} diff --git a/internal/admission/controller/main_test.go b/internal/admission/controller/main_test.go new file mode 100644 index 0000000000..1fd6fc488a --- /dev/null +++ b/internal/admission/controller/main_test.go @@ -0,0 +1,109 @@ +/* +Copyright 2019 The Kubernetes 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 controller + +import ( + "fmt" + "testing" + + "k8s.io/api/admission/v1beta1" + extensions "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/json" +) + +const testIngressName = "testIngressName" + +type failTestChecker struct { + t *testing.T +} + +func (ftc failTestChecker) CheckIngress(ing *extensions.Ingress) error { + ftc.t.Error("checker should not be called") + return nil +} + +type testChecker struct { + t *testing.T + err error +} + +func (tc testChecker) CheckIngress(ing *extensions.Ingress) error { + if ing.ObjectMeta.Name != testIngressName { + tc.t.Errorf("CheckIngress should be called with %v ingress, but got %v", testIngressName, ing.ObjectMeta.Name) + } + return tc.err +} + +func TestHandleAdmission(t *testing.T) { + adm := &IngressAdmission{ + Checker: failTestChecker{t: t}, + } + review := &v1beta1.AdmissionReview{ + Request: &v1beta1.AdmissionRequest{ + Resource: v1.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"}, + }, + } + err := adm.HandleAdmission(review) + if !review.Response.Allowed { + t.Errorf("with a non ingress resource, the check should pass") + } + if err != nil { + t.Errorf("with a non ingress resource, no error should be returned") + } + + review.Request.Resource = v1.GroupVersionResource{Group: extensions.SchemeGroupVersion.Group, Version: extensions.SchemeGroupVersion.Version, Resource: "ingresses"} + review.Request.Object.Raw = []byte{0xff} + + err = adm.HandleAdmission(review) + if review.Response.Allowed { + t.Errorf("when the request object is not decodable, the request should not be allowed") + } + if err == nil { + t.Errorf("when the request object is not decodable, an error should be returned") + } + + raw, err := json.Marshal(extensions.Ingress{ObjectMeta: v1.ObjectMeta{Name: testIngressName}}) + if err != nil { + t.Errorf("failed to prepare test ingress data: %v", err.Error()) + } + review.Request.Object.Raw = raw + + adm.Checker = testChecker{ + t: t, + err: fmt.Errorf("this is a test error"), + } + err = adm.HandleAdmission(review) + if review.Response.Allowed { + t.Errorf("when the checker returns an error, the request should not be allowed") + } + if err == nil { + t.Errorf("when the checker returns an error, an error should be returned") + } + + adm.Checker = testChecker{ + t: t, + err: nil, + } + err = adm.HandleAdmission(review) + if !review.Response.Allowed { + t.Errorf("when the checker returns no error, the request should be allowed") + } + if err != nil { + t.Errorf("when the checker returns no error, no error should be returned") + } +} diff --git a/internal/admission/controller/server.go b/internal/admission/controller/server.go new file mode 100644 index 0000000000..5031dca58f --- /dev/null +++ b/internal/admission/controller/server.go @@ -0,0 +1,89 @@ +/* +Copyright 2019 The Kubernetes 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 controller + +import ( + "io" + "io/ioutil" + "net/http" + + "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/klog" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +// AdmissionController checks if an object +// is allowed in the cluster +type AdmissionController interface { + HandleAdmission(*v1beta1.AdmissionReview) error +} + +// AdmissionControllerServer implements an HTTP server +// for kubernetes validating webhook +// https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook +type AdmissionControllerServer struct { + AdmissionController AdmissionController + Decoder runtime.Decoder +} + +// NewAdmissionControllerServer instanciates an admission controller server with +// a default codec +func NewAdmissionControllerServer(ac AdmissionController) *AdmissionControllerServer { + return &AdmissionControllerServer{ + AdmissionController: ac, + Decoder: codecs.UniversalDeserializer(), + } +} + +// ServeHTTP implements http.Server method +func (acs *AdmissionControllerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + klog.Infof("handling admission controller request %s", r.URL.String()) + + review, err := parseAdmissionReview(acs.Decoder, r.Body) + if err != nil { + klog.Error("Can't decode request", err) + w.WriteHeader(http.StatusBadRequest) + return + } + + acs.AdmissionController.HandleAdmission(review) + if err := writeAdmissionReview(w, review); err != nil { + klog.Error(err) + } +} + +func parseAdmissionReview(decoder runtime.Decoder, r io.Reader) (*v1beta1.AdmissionReview, error) { + review := &v1beta1.AdmissionReview{} + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + _, _, err = decoder.Decode(data, nil, review) + return review, err +} + +func writeAdmissionReview(w io.Writer, ar *v1beta1.AdmissionReview) error { + e := json.NewEncoder(w) + return e.Encode(ar) +} diff --git a/internal/admission/controller/server_test.go b/internal/admission/controller/server_test.go new file mode 100644 index 0000000000..4ec2893d6d --- /dev/null +++ b/internal/admission/controller/server_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2019 The Kubernetes 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 controller + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/google/uuid" + "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/types" +) + +type testAdmissionHandler struct{} + +func (testAdmissionHandler) HandleAdmission(ar *v1beta1.AdmissionReview) error { + ar.Response = &v1beta1.AdmissionResponse{ + UID: types.UID(uuid.New().String()), + Allowed: true, + } + return nil +} + +type errorReader struct{} + +func (errorReader) Read(p []byte) (n int, err error) { + return 0, fmt.Errorf("this is a test error") +} + +type errorWriter struct{} + +func (errorWriter) Write(p []byte) (n int, err error) { + return 0, fmt.Errorf("this is a test error") +} + +func (errorWriter) Header() http.Header { + return nil +} + +func (errorWriter) WriteHeader(statusCode int) {} + +func TestServer(t *testing.T) { + w := httptest.NewRecorder() + b := bytes.NewBuffer(nil) + writeAdmissionReview(b, &v1beta1.AdmissionReview{}) + + // Happy path + r := httptest.NewRequest("GET", "http://test.ns.svc", b) + NewAdmissionControllerServer(testAdmissionHandler{}).ServeHTTP(w, r) + ar, err := parseAdmissionReview(codecs.UniversalDeserializer(), w.Body) + if w.Code != http.StatusOK { + t.Errorf("when the admission review allows the request, the http status should be OK") + } + if err != nil { + t.Errorf("failed to parse admission response when the admission controller returns a value") + } + if !ar.Response.Allowed { + t.Errorf("when the admission review allows the request, the parsed body returns not allowed") + } + + // Ensure the code does not panic when failing to handle the request + NewAdmissionControllerServer(testAdmissionHandler{}).ServeHTTP(errorWriter{}, r) + + w = httptest.NewRecorder() + NewAdmissionControllerServer(testAdmissionHandler{}).ServeHTTP(w, httptest.NewRequest("GET", "http://test.ns.svc", strings.NewReader("invalid-json"))) + if w.Code != http.StatusBadRequest { + t.Errorf("when the server fails to read the request, the replied status should be bad request") + } +} + +func TestParseAdmissionReview(t *testing.T) { + ar, err := parseAdmissionReview(codecs.UniversalDeserializer(), errorReader{}) + if ar != nil { + t.Errorf("when reading from request fails, no AdmissionRewiew should be returned") + } + if err == nil { + t.Errorf("when reading from request fails, an error should be returned") + } +} diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index a37ad28e20..f0fc95d839 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -23,23 +23,21 @@ import ( "strings" "time" - "k8s.io/ingress-nginx/internal/ingress/annotations/log" - "github.com/mitchellh/hashstructure" - "k8s.io/klog" - apiv1 "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" - "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations" + "k8s.io/ingress-nginx/internal/ingress/annotations/class" + "k8s.io/ingress-nginx/internal/ingress/annotations/log" "k8s.io/ingress-nginx/internal/ingress/annotations/proxy" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" "k8s.io/ingress-nginx/internal/k8s" + "k8s.io/klog" ) const ( @@ -96,6 +94,10 @@ type Configuration struct { DynamicCertificatesEnabled bool DisableCatchAll bool + + ValidationWebhook string + ValidationWebhookCertPath string + ValidationWebhookKeyPath string } // GetPublishService returns the Service used to set the load-balancer status of Ingresses. @@ -120,47 +122,7 @@ func (n *NGINXController) syncIngress(interface{}) error { ings := n.store.ListIngresses() - upstreams, servers := n.getBackendServers(ings) - var passUpstreams []*ingress.SSLPassthroughBackend - - hosts := sets.NewString() - - for _, server := range servers { - if !hosts.Has(server.Hostname) { - hosts.Insert(server.Hostname) - } - if server.Alias != "" && !hosts.Has(server.Alias) { - hosts.Insert(server.Alias) - } - - if !server.SSLPassthrough { - continue - } - - for _, loc := range server.Locations { - if loc.Path != rootLocation { - klog.Warningf("Ignoring SSL Passthrough for location %q in server %q", loc.Path, server.Hostname) - continue - } - passUpstreams = append(passUpstreams, &ingress.SSLPassthroughBackend{ - Backend: loc.Backend, - Hostname: server.Hostname, - Service: loc.Service, - Port: loc.Port, - }) - break - } - } - - pcfg := &ingress.Configuration{ - Backends: upstreams, - Servers: servers, - TCPEndpoints: n.getStreamServices(n.cfg.TCPConfigMapName, apiv1.ProtocolTCP), - UDPEndpoints: n.getStreamServices(n.cfg.UDPConfigMapName, apiv1.ProtocolUDP), - PassthroughBackends: passUpstreams, - BackendConfigChecksum: n.store.GetBackendConfiguration().Checksum, - ControllerPodsCount: n.store.GetRunningControllerPodsCount(), - } + hosts, servers, pcfg := n.getConfiguration(ings) if n.runningConfig.Equal(pcfg) { klog.V(3).Infof("No configuration change detected, skipping backend reload.") @@ -235,6 +197,60 @@ func (n *NGINXController) syncIngress(interface{}) error { return nil } +// CheckIngress returns an error in case the provided ingress, when added +// to the current configuration, generates an invalid configuration +func (n *NGINXController) CheckIngress(ing *extensions.Ingress) error { + if n == nil { + return fmt.Errorf("cannot check ingress on a nil ingress controller") + } + if ing == nil { + // no ingress to add, no state change + return nil + } + if !class.IsValid(ing) { + klog.Infof("ignoring ingress %v in %v based on annotation %v", ing.Name, ing.ObjectMeta.Namespace, class.IngressKey) + return nil + } + if n.cfg.Namespace != "" && ing.ObjectMeta.Namespace != n.cfg.Namespace { + klog.Infof("ignoring ingress %v in namespace %v different from the namespace watched %s", ing.Name, ing.ObjectMeta.Namespace, n.cfg.Namespace) + return nil + } + + ings := n.store.ListIngresses() + newIngress := &ingress.Ingress{ + Ingress: *ing, + ParsedAnnotations: annotations.NewAnnotationExtractor(n.store).Extract(ing), + } + + for i, ingress := range ings { + if ingress.Ingress.ObjectMeta.Name == ing.ObjectMeta.Name && ingress.Ingress.ObjectMeta.Namespace == ing.ObjectMeta.Namespace { + ings[i] = newIngress + newIngress = nil + } + } + if newIngress != nil { + ings = append(ings, newIngress) + } + + _, _, pcfg := n.getConfiguration(ings) + + cfg := n.store.GetBackendConfiguration() + cfg.Resolver = n.resolver + + content, err := n.generateTemplate(cfg, *pcfg) + if err != nil { + n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name) + return err + } + err = n.testTemplate(content) + if err != nil { + n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name) + } else { + n.metricCollector.IncCheckCount(ing.ObjectMeta.Namespace, ing.Name) + } + return err +} + func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service { if configmapName == "" { return []ingress.L4Service{} @@ -380,6 +396,51 @@ func (n *NGINXController) getDefaultUpstream() *ingress.Backend { return upstream } +// getConfiguration returns the configuration matching the standard kubernetes ingress +func (n *NGINXController) getConfiguration(ingresses []*ingress.Ingress) (sets.String, []*ingress.Server, *ingress.Configuration) { + upstreams, servers := n.getBackendServers(ingresses) + var passUpstreams []*ingress.SSLPassthroughBackend + + hosts := sets.NewString() + + for _, server := range servers { + if !hosts.Has(server.Hostname) { + hosts.Insert(server.Hostname) + } + if server.Alias != "" && !hosts.Has(server.Alias) { + hosts.Insert(server.Alias) + } + + if !server.SSLPassthrough { + continue + } + + for _, loc := range server.Locations { + if loc.Path != rootLocation { + klog.Warningf("Ignoring SSL Passthrough for location %q in server %q", loc.Path, server.Hostname) + continue + } + passUpstreams = append(passUpstreams, &ingress.SSLPassthroughBackend{ + Backend: loc.Backend, + Hostname: server.Hostname, + Service: loc.Service, + Port: loc.Port, + }) + break + } + } + + return hosts, servers, &ingress.Configuration{ + Backends: upstreams, + Servers: servers, + TCPEndpoints: n.getStreamServices(n.cfg.TCPConfigMapName, apiv1.ProtocolTCP), + UDPEndpoints: n.getStreamServices(n.cfg.UDPConfigMapName, apiv1.ProtocolUDP), + PassthroughBackends: passUpstreams, + BackendConfigChecksum: n.store.GetBackendConfiguration().Checksum, + ControllerPodsCount: n.store.GetRunningControllerPodsCount(), + } +} + // getBackendServers returns a list of Upstream and Server to be used by the // backend. An upstream can be used in multiple servers if the namespace, // service name and port are the same. diff --git a/internal/ingress/controller/controller_test.go b/internal/ingress/controller/controller_test.go index ef9dbca0ac..43387f5856 100644 --- a/internal/ingress/controller/controller_test.go +++ b/internal/ingress/controller/controller_test.go @@ -21,12 +21,17 @@ import ( "crypto/x509/pkix" "encoding/asn1" "fmt" - "time" - + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" "testing" + "time" "github.com/eapache/channels" "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -35,14 +40,216 @@ import ( "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations" "k8s.io/ingress-nginx/internal/ingress/annotations/canary" + "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" "k8s.io/ingress-nginx/internal/ingress/controller/store" + "k8s.io/ingress-nginx/internal/ingress/defaults" + "k8s.io/ingress-nginx/internal/ingress/metric" + "k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/net/ssl" ) const fakeCertificateName = "default-fake-certificate" +type fakeIngressStore struct { + ingresses []*ingress.Ingress +} + +func (fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration { + return ngx_config.Configuration{} +} + +func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) { + return nil, fmt.Errorf("test error") +} + +func (fakeIngressStore) GetSecret(key string) (*corev1.Secret, error) { + return nil, fmt.Errorf("test error") +} + +func (fakeIngressStore) GetService(key string) (*corev1.Service, error) { + return nil, fmt.Errorf("test error") +} + +func (fakeIngressStore) GetServiceEndpoints(key string) (*corev1.Endpoints, error) { + return nil, fmt.Errorf("test error") +} + +func (fis fakeIngressStore) ListIngresses() []*ingress.Ingress { + return fis.ingresses +} + +func (fakeIngressStore) GetRunningControllerPodsCount() int { + return 0 +} + +func (fakeIngressStore) GetLocalSSLCert(name string) (*ingress.SSLCert, error) { + return nil, fmt.Errorf("test error") +} + +func (fakeIngressStore) ListLocalSSLCerts() []*ingress.SSLCert { + return nil +} + +func (fakeIngressStore) GetAuthCertificate(string) (*resolver.AuthSSLCert, error) { + return nil, fmt.Errorf("test error") +} + +func (fakeIngressStore) GetDefaultBackend() defaults.Backend { + return defaults.Backend{} +} + +func (fakeIngressStore) Run(stopCh chan struct{}) {} + +type testNginxTestCommand struct { + t *testing.T + expected string + out []byte + err error +} + +func (ntc testNginxTestCommand) ExecCommand(args ...string) *exec.Cmd { + return nil +} + +func (ntc testNginxTestCommand) Test(cfg string) ([]byte, error) { + fd, err := os.Open(cfg) + if err != nil { + ntc.t.Errorf("could not read generated nginx configuration: %v", err.Error()) + return nil, err + } + defer fd.Close() + bytes, err := ioutil.ReadAll(fd) + if err != nil { + ntc.t.Errorf("could not read generated nginx configuration: %v", err.Error()) + } + if string(bytes) != ntc.expected { + ntc.t.Errorf("unexpected generated configuration %v. Expecting %v", string(bytes), ntc.expected) + } + return ntc.out, ntc.err +} + +type fakeTemplate struct{} + +func (fakeTemplate) Write(conf config.TemplateConfig) ([]byte, error) { + r := []byte{} + for _, s := range conf.Servers { + if len(r) > 0 { + r = append(r, ',') + } + r = append(r, []byte(s.Hostname)...) + } + return r, nil +} + +func TestCheckIngress(t *testing.T) { + defer func() { + filepath.Walk(os.TempDir(), func(path string, info os.FileInfo, err error) error { + if info.IsDir() && os.TempDir() != path { + return filepath.SkipDir + } + if strings.HasPrefix(info.Name(), tempNginxPattern) { + os.Remove(path) + } + return nil + }) + }() + + // Ensure no panic with wrong arguments + var nginx *NGINXController + nginx.CheckIngress(nil) + nginx = newNGINXController(t) + nginx.CheckIngress(nil) + nginx.metricCollector = metric.DummyCollector{} + + nginx.t = fakeTemplate{} + nginx.store = fakeIngressStore{ + ingresses: []*ingress.Ingress{}, + } + + ing := &extensions.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: "user-namespace", + Annotations: map[string]string{}, + }, + Spec: extensions.IngressSpec{ + Rules: []extensions.IngressRule{ + { + Host: "example.com", + }, + }, + }, + } + + t.Run("When the ingress class differs from nginx", func(t *testing.T) { + ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "different" + nginx.command = testNginxTestCommand{ + t: t, + err: fmt.Errorf("test error"), + } + if nginx.CheckIngress(ing) != nil { + t.Errorf("with a different ingress class, no error should be returned") + } + }) + + t.Run("when the class is the nginx one", func(t *testing.T) { + ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "nginx" + nginx.command = testNginxTestCommand{ + t: t, + err: nil, + expected: "_,example.com", + } + if nginx.CheckIngress(ing) != nil { + t.Errorf("with a new ingress without error, no error should be returned") + } + + t.Run("When the hostname is updated", func(t *testing.T) { + nginx.store = fakeIngressStore{ + ingresses: []*ingress.Ingress{ + { + Ingress: *ing, + }, + }, + } + ing.Spec.Rules[0].Host = "test.example.com" + nginx.command = testNginxTestCommand{ + t: t, + err: nil, + expected: "_,test.example.com", + } + if nginx.CheckIngress(ing) != nil { + t.Errorf("with a new ingress without error, no error should be returned") + } + }) + + t.Run("When nginx test returns an error", func(t *testing.T) { + nginx.command = testNginxTestCommand{ + t: t, + err: fmt.Errorf("test error"), + out: []byte("this is the test command output"), + expected: "_,test.example.com", + } + if nginx.CheckIngress(ing) == nil { + t.Errorf("with a new ingress with an error, an error should be returned") + } + }) + + t.Run("When the ingress is in a different namespace than the watched one", func(t *testing.T) { + nginx.command = testNginxTestCommand{ + t: t, + err: fmt.Errorf("test error"), + } + nginx.cfg.Namespace = "other-namespace" + ing.ObjectMeta.Namespace = "test-namespace" + if nginx.CheckIngress(ing) != nil { + t.Errorf("with a new ingress without error, no error should be returned") + } + }) + }) +} + func TestMergeAlternativeBackends(t *testing.T) { testCases := map[string]struct { ingress *ingress.Ingress @@ -930,8 +1137,10 @@ func newNGINXController(t *testing.T) *NGINXController { } return &NGINXController{ - store: storer, - cfg: config, + store: storer, + cfg: config, + command: NewNginxCommand(), + fileSystem: fs, } } diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go index 5b47407f07..34c23ad503 100644 --- a/internal/ingress/controller/nginx.go +++ b/internal/ingress/controller/nginx.go @@ -45,9 +45,7 @@ import ( v1core "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/flowcontrol" - "k8s.io/klog" - "k8s.io/kubernetes/pkg/util/filesystem" - + adm_controler "k8s.io/ingress-nginx/internal/admission/controller" "k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations/class" @@ -64,6 +62,8 @@ import ( "k8s.io/ingress-nginx/internal/nginx" "k8s.io/ingress-nginx/internal/task" "k8s.io/ingress-nginx/internal/watch" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/util/filesystem" ) const ( @@ -110,6 +110,16 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File Proxy: &TCPProxy{}, metricCollector: mc, + + command: NewNginxCommand(), + } + + if n.cfg.ValidationWebhook != "" { + n.validationWebhookServer = &http.Server{ + Addr: config.ValidationWebhook, + Handler: adm_controler.NewAdmissionControllerServer(&adm_controler.IngressAdmission{Checker: n}), + TLSConfig: ssl.NewTLSListener(n.cfg.ValidationWebhookCertPath, n.cfg.ValidationWebhookKeyPath).TLSConfig(), + } } pod, err := k8s.GetPodDetails(config.Client) @@ -241,7 +251,7 @@ type NGINXController struct { // runningConfig contains the running configuration in the Backend runningConfig *ingress.Configuration - t *ngx_template.Template + t ngx_template.TemplateWriter resolver []net.IP @@ -258,6 +268,10 @@ type NGINXController struct { metricCollector metric.Collector currentLeader uint32 + + validationWebhookServer *http.Server + + command NginxExecTester } // Start starts a new NGINX master process running in the foreground. @@ -295,7 +309,7 @@ func (n *NGINXController) Start() { PodNamespace: n.podInfo.Namespace, }) - cmd := nginxExecCommand() + cmd := n.command.ExecCommand() // put NGINX in another process group to prevent it // to receive signals meant for the controller @@ -327,6 +341,13 @@ func (n *NGINXController) Start() { } }() + if n.validationWebhookServer != nil { + klog.Infof("Starting validation webhook on %s with keys %s %s", n.validationWebhookServer.Addr, n.cfg.ValidationWebhookCertPath, n.cfg.ValidationWebhookKeyPath) + go func() { + klog.Error(n.validationWebhookServer.ListenAndServeTLS("", "")) + }() + } + for { select { case err := <-n.ngxErrCh: @@ -344,7 +365,7 @@ func (n *NGINXController) Start() { // release command resources cmd.Process.Release() // start a new nginx master process if the controller is not being stopped - cmd = nginxExecCommand() + cmd = n.command.ExecCommand() cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, Pgid: 0, @@ -391,9 +412,17 @@ func (n *NGINXController) Stop() error { n.syncStatus.Shutdown() } + if n.validationWebhookServer != nil { + klog.Info("Stopping admission controller") + err := n.validationWebhookServer.Close() + if err != nil { + return err + } + } + // send stop signal to NGINX klog.Info("Stopping NGINX process") - cmd := nginxExecCommand("-s", "quit") + cmd := n.command.ExecCommand("-s", "quit") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() @@ -437,45 +466,8 @@ func (n NGINXController) DefaultEndpoint() ingress.Endpoint { } } -// testTemplate checks if the NGINX configuration inside the byte array is valid -// running the command "nginx -t" using a temporal file. -func (n NGINXController) testTemplate(cfg []byte) error { - if len(cfg) == 0 { - return fmt.Errorf("invalid NGINX configuration (empty)") - } - tmpfile, err := ioutil.TempFile("", tempNginxPattern) - if err != nil { - return err - } - defer tmpfile.Close() - err = ioutil.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser) - if err != nil { - return err - } - out, err := nginxTestCommand(tmpfile.Name()).CombinedOutput() - if err != nil { - // this error is different from the rest because it must be clear why nginx is not working - oe := fmt.Sprintf(` -------------------------------------------------------------------------------- -Error: %v -%v -------------------------------------------------------------------------------- -`, err, string(out)) - - return errors.New(oe) - } - - os.Remove(tmpfile.Name()) - return nil -} - -// OnUpdate is called by the synchronization loop whenever configuration -// changes were detected. The received backend Configuration is merged with the -// configuration ConfigMap before generating the final configuration file. -// Returns nil in case the backend was successfully reloaded. -func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { - cfg := n.store.GetBackendConfiguration() - cfg.Resolver = n.resolver +// generateTemplate returns the nginx configuration file content +func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressCfg ingress.Configuration) ([]byte, error) { if n.cfg.EnableSSLPassthrough { servers := []*TCPServer{} @@ -638,7 +630,50 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { tc.Cfg.Checksum = ingressCfg.ConfigurationChecksum - content, err := n.t.Write(tc) + return n.t.Write(tc) +} + +// testTemplate checks if the NGINX configuration inside the byte array is valid +// running the command "nginx -t" using a temporal file. +func (n NGINXController) testTemplate(cfg []byte) error { + if len(cfg) == 0 { + return fmt.Errorf("invalid NGINX configuration (empty)") + } + tmpfile, err := ioutil.TempFile("", tempNginxPattern) + if err != nil { + return err + } + defer tmpfile.Close() + err = ioutil.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser) + if err != nil { + return err + } + out, err := n.command.Test(tmpfile.Name()) + if err != nil { + // this error is different from the rest because it must be clear why nginx is not working + oe := fmt.Sprintf(` +------------------------------------------------------------------------------- +Error: %v +%v +------------------------------------------------------------------------------- +`, err, string(out)) + + return errors.New(oe) + } + + os.Remove(tmpfile.Name()) + return nil +} + +// OnUpdate is called by the synchronization loop whenever configuration +// changes were detected. The received backend Configuration is merged with the +// configuration ConfigMap before generating the final configuration file. +// Returns nil in case the backend was successfully reloaded. +func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { + cfg := n.store.GetBackendConfiguration() + cfg.Resolver = n.resolver + + content, err := n.generateTemplate(cfg, ingressCfg) if err != nil { return err } @@ -686,7 +721,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { return err } - o, err := nginxExecCommand("-s", "reload").CombinedOutput() + o, err := n.command.ExecCommand("-s", "reload").CombinedOutput() if err != nil { return fmt.Errorf("%v\n%v", err, string(o)) } diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index 2ee6a3ee2e..2183715c15 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -51,6 +51,11 @@ const ( defBufferSize = 65535 ) +// TemplateWriter is the interface to render a template +type TemplateWriter interface { + Write(conf config.TemplateConfig) ([]byte, error) +} + // Template ... type Template struct { tmpl *text_template.Template diff --git a/internal/ingress/controller/util.go b/internal/ingress/controller/util.go index 5479d47a79..bfbb338fe1 100644 --- a/internal/ingress/controller/util.go +++ b/internal/ingress/controller/util.go @@ -21,13 +21,11 @@ import ( "os/exec" "syscall" - "k8s.io/klog" - api "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/kubernetes/pkg/util/sysctl" - "k8s.io/ingress-nginx/internal/ingress" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/util/sysctl" ) // newUpstream creates an upstream without servers. @@ -79,14 +77,36 @@ const ( cfgPath = "/etc/nginx/nginx.conf" ) -func nginxExecCommand(args ...string) *exec.Cmd { +// NginxExecTester defines the interface to execute +// command like reload or test configuration +type NginxExecTester interface { + ExecCommand(args ...string) *exec.Cmd + Test(cfg string) ([]byte, error) +} + +// NginxCommand stores context around a given nginx executable path +type NginxCommand struct { + Binary string +} + +// NewNginxCommand returns a new NginxCommand from which path +// has been detected from environment variable NGINX_BINARY or default +func NewNginxCommand() NginxCommand { + return NginxCommand{ + Binary: defBinary, + } +} + +// ExecCommand instanciates an exec.Cmd object to call nginx program +func (nc NginxCommand) ExecCommand(args ...string) *exec.Cmd { cmdArgs := []string{} cmdArgs = append(cmdArgs, "-c", cfgPath) cmdArgs = append(cmdArgs, args...) - return exec.Command(defBinary, cmdArgs...) + return exec.Command(nc.Binary, cmdArgs...) } -func nginxTestCommand(cfg string) *exec.Cmd { - return exec.Command(defBinary, "-c", cfg, "-t") +// Test checks if config file is a syntax valid nginx configuration +func (nc NginxCommand) Test(cfg string) ([]byte, error) { + return exec.Command(nc.Binary, "-c", cfg, "-t").CombinedOutput() } diff --git a/internal/ingress/metric/collectors/controller.go b/internal/ingress/metric/collectors/controller.go index 3df99bafda..e64a271ff7 100644 --- a/internal/ingress/metric/collectors/controller.go +++ b/internal/ingress/metric/collectors/controller.go @@ -21,15 +21,15 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - "k8s.io/klog" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/ingress-nginx/internal/ingress" + "k8s.io/klog" ) var ( - operation = []string{"controller_namespace", "controller_class", "controller_pod"} - sslLabelHost = []string{"namespace", "class", "host"} + operation = []string{"controller_namespace", "controller_class", "controller_pod"} + ingressOperation = []string{"controller_namespace", "controller_class", "controller_pod", "namespace", "ingress"} + sslLabelHost = []string{"namespace", "class", "host"} ) // Controller defines base metrics about the ingress controller @@ -40,9 +40,11 @@ type Controller struct { configSuccess prometheus.Gauge configSuccessTime prometheus.Gauge - reloadOperation *prometheus.CounterVec - reloadOperationErrors *prometheus.CounterVec - sslExpireTime *prometheus.GaugeVec + reloadOperation *prometheus.CounterVec + reloadOperationErrors *prometheus.CounterVec + checkIngressOperation *prometheus.CounterVec + checkIngressOperationErrors *prometheus.CounterVec + sslExpireTime *prometheus.GaugeVec constLabels prometheus.Labels labels prometheus.Labels @@ -105,6 +107,22 @@ func NewController(pod, namespace, class string) *Controller { }, operation, ), + checkIngressOperationErrors: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: PrometheusNamespace, + Name: "check_errors", + Help: `Cumulative number of Ingress controller errors during syntax check operations`, + }, + ingressOperation, + ), + checkIngressOperation: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: PrometheusNamespace, + Name: "check_success", + Help: `Cumulative number of Ingress controller syntax check operations`, + }, + ingressOperation, + ), sslExpireTime: prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: PrometheusNamespace, @@ -148,6 +166,24 @@ func (cm *Controller) OnStoppedLeading(electionID string) { cm.leaderElection.WithLabelValues(electionID).Set(0) } +// IncCheckCount increment the check counter +func (cm *Controller) IncCheckCount(namespace, name string) { + labels := prometheus.Labels{ + "namespace": namespace, + "ingress": name, + } + cm.checkIngressOperation.MustCurryWith(cm.constLabels).With(labels).Inc() +} + +// IncCheckErrorCount increment the check error counter +func (cm *Controller) IncCheckErrorCount(namespace, name string) { + labels := prometheus.Labels{ + "namespace": namespace, + "ingress": name, + } + cm.checkIngressOperationErrors.MustCurryWith(cm.constLabels).With(labels).Inc() +} + // ConfigSuccess set a boolean flag according to the output of the controller configuration reload func (cm *Controller) ConfigSuccess(hash uint64, success bool) { if success { @@ -170,6 +206,8 @@ func (cm Controller) Describe(ch chan<- *prometheus.Desc) { cm.configSuccessTime.Describe(ch) cm.reloadOperation.Describe(ch) cm.reloadOperationErrors.Describe(ch) + cm.checkIngressOperation.Describe(ch) + cm.checkIngressOperationErrors.Describe(ch) cm.sslExpireTime.Describe(ch) cm.leaderElection.Describe(ch) } @@ -181,6 +219,8 @@ func (cm Controller) Collect(ch chan<- prometheus.Metric) { cm.configSuccessTime.Collect(ch) cm.reloadOperation.Collect(ch) cm.reloadOperationErrors.Collect(ch) + cm.checkIngressOperation.Collect(ch) + cm.checkIngressOperationErrors.Collect(ch) cm.sslExpireTime.Collect(ch) cm.leaderElection.Collect(ch) } diff --git a/internal/ingress/metric/dummy.go b/internal/ingress/metric/dummy.go index 46ce3f7a8e..59a9144e0c 100644 --- a/internal/ingress/metric/dummy.go +++ b/internal/ingress/metric/dummy.go @@ -38,6 +38,12 @@ func (dc DummyCollector) IncReloadCount() {} // IncReloadErrorCount ... func (dc DummyCollector) IncReloadErrorCount() {} +// IncCheckCount ... +func (dc DummyCollector) IncCheckCount(string, string) {} + +// IncCheckErrorCount ... +func (dc DummyCollector) IncCheckErrorCount(string, string) {} + // RemoveMetrics ... func (dc DummyCollector) RemoveMetrics(ingresses, endpoints []string) {} diff --git a/internal/ingress/metric/main.go b/internal/ingress/metric/main.go index 8039c2d743..e30fdc7835 100644 --- a/internal/ingress/metric/main.go +++ b/internal/ingress/metric/main.go @@ -21,9 +21,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress/annotations/class" "k8s.io/ingress-nginx/internal/ingress/metric/collectors" @@ -39,6 +37,9 @@ type Collector interface { OnStartedLeading(string) OnStoppedLeading(string) + IncCheckCount(string, string) + IncCheckErrorCount(string, string) + RemoveMetrics(ingresses, endpoints []string) SetSSLExpireTime([]*ingress.Server) @@ -103,6 +104,14 @@ func (c *collector) ConfigSuccess(hash uint64, success bool) { c.ingressController.ConfigSuccess(hash, success) } +func (c *collector) IncCheckCount(namespace string, name string) { + c.ingressController.IncCheckCount(namespace, name) +} + +func (c *collector) IncCheckErrorCount(namespace string, name string) { + c.ingressController.IncCheckErrorCount(namespace, name) +} + func (c *collector) IncReloadCount() { c.ingressController.IncReloadCount() } diff --git a/internal/net/ssl/ssl.go b/internal/net/ssl/ssl.go index 56018cc78a..e5b2318ed0 100644 --- a/internal/net/ssl/ssl.go +++ b/internal/net/ssl/ssl.go @@ -31,15 +31,15 @@ import ( "net" "strconv" "strings" + "sync" "time" "github.com/zakjan/cert-chain-resolver/certUtil" - "k8s.io/klog" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/ingress" + "k8s.io/ingress-nginx/internal/watch" + "k8s.io/klog" ) var ( @@ -360,6 +360,23 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err // GetFakeSSLCert creates a Self Signed Certificate // Based in the code https://golang.org/src/crypto/tls/generate_cert.go func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert { + cert, key := getFakeHostSSLCert("ingress.local") + + sslCert, err := CreateSSLCert(cert, key) + if err != nil { + klog.Fatalf("unexpected error creating fake SSL Cert: %v", err) + } + + err = StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert) + if err != nil { + klog.Fatalf("unexpected error storing fake SSL Cert: %v", err) + } + + return sslCert +} + +func getFakeHostSSLCert(host string) ([]byte, []byte) { + var priv interface{} var err error @@ -392,7 +409,7 @@ func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert { KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, - DNSNames: []string{"ingress.local"}, + DNSNames: []string{host}, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.(*rsa.PrivateKey).PublicKey, priv) if err != nil { @@ -403,17 +420,7 @@ func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert { key := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv.(*rsa.PrivateKey))}) - sslCert, err := CreateSSLCert(cert, key) - if err != nil { - klog.Fatalf("unexpected error creating fake SSL Cert: %v", err) - } - - err = StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert) - if err != nil { - klog.Fatalf("unexpected error storing fake SSL Cert: %v", err) - } - - return sslCert + return cert, key } // FullChainCert checks if a certificate file contains issues in the intermediate CA chain @@ -470,3 +477,64 @@ func IsValidHostname(hostname string, commonNames []string) bool { return false } + +// TLSListener implements a dynamic certificate loader +type TLSListener struct { + certificatePath string + keyPath string + fs file.Filesystem + certificate *tls.Certificate + err error + lock sync.Mutex +} + +// NewTLSListener watches changes to th certificate and key paths +// and reloads it whenever it changes +func NewTLSListener(certificate, key string) *TLSListener { + fs, err := file.NewLocalFS() + if err != nil { + panic(fmt.Sprintf("failed to instanciate certificate: %v", err)) + } + l := TLSListener{ + certificatePath: certificate, + keyPath: key, + fs: fs, + lock: sync.Mutex{}, + } + l.load() + watch.NewFileWatcher(certificate, l.load) + watch.NewFileWatcher(key, l.load) + return &l +} + +// GetCertificate implements the tls.Config.GetCertificate interface +func (tl *TLSListener) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { + tl.lock.Lock() + defer tl.lock.Unlock() + return tl.certificate, tl.err +} + +// TLSConfig instanciates a TLS configuration, always providing an up to date certificate +func (tl *TLSListener) TLSConfig() *tls.Config { + return &tls.Config{ + GetCertificate: tl.GetCertificate, + } +} + +func (tl *TLSListener) load() { + klog.Infof("loading tls certificate from certificate path %s and key path %s", tl.certificatePath, tl.keyPath) + certBytes, err := tl.fs.ReadFile(tl.certificatePath) + if err != nil { + tl.certificate = nil + tl.err = err + } + keyBytes, err := tl.fs.ReadFile(tl.keyPath) + if err != nil { + tl.certificate = nil + tl.err = err + } + cert, err := tls.X509KeyPair(certBytes, keyBytes) + tl.lock.Lock() + defer tl.lock.Unlock() + tl.certificate, tl.err = &cert, err +} diff --git a/internal/net/ssl/ssl_test.go b/internal/net/ssl/ssl_test.go index e88df95f25..e2ca147859 100644 --- a/internal/net/ssl/ssl_test.go +++ b/internal/net/ssl/ssl_test.go @@ -22,6 +22,7 @@ import ( "crypto/rand" cryptorand "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -29,10 +30,15 @@ import ( "fmt" "math" "math/big" + "net/http" + "net/http/httptest" + "strings" + "sync" "testing" "time" certutil "k8s.io/client-go/util/cert" + "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/ingress-nginx/internal/file" ) @@ -366,3 +372,87 @@ func encodeCertPEM(cert *x509.Certificate) []byte { } return pem.EncodeToMemory(&block) } + +func fakeCertificate(t *testing.T, fs filesystem.Filesystem) []byte { + cert, key := getFakeHostSSLCert("localhost") + fd, err := fs.Create("/key.crt") + if err != nil { + t.Errorf("failed to write test key: %v", err) + } + fd.Write(cert) + fd, err = fs.Create("/key.key") + if err != nil { + t.Errorf("failed to write test key: %v", err) + } + fd.Write(key) + return cert +} + +func dialTestServer(port string, rootCertificates ...[]byte) error { + roots := x509.NewCertPool() + for _, cert := range rootCertificates { + ok := roots.AppendCertsFromPEM(cert) + if !ok { + return fmt.Errorf("failed to add root certificate") + } + } + resp, err := tls.Dial("tcp", "localhost:"+port, &tls.Config{ + RootCAs: roots, + }) + if err != nil { + return err + } + if resp.Handshake() != nil { + return fmt.Errorf("TLS handshake should succeed: %v", err) + } + return nil +} + +func TestTLSKeyReloader(t *testing.T) { + fs := filesystem.NewFakeFs() + cert := fakeCertificate(t, fs) + + watcher := TLSListener{ + certificatePath: "/key.crt", + keyPath: "/key.key", + fs: fs, + lock: sync.Mutex{}, + } + watcher.load() + + s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + s.Config.TLSConfig = watcher.TLSConfig() + s.Listener = tls.NewListener(s.Listener, s.Config.TLSConfig) + go s.Start() + defer s.Close() + port := strings.Split(s.Listener.Addr().String(), ":")[1] + + t.Run("without the trusted certificate", func(t *testing.T) { + if dialTestServer(port) == nil { + t.Errorf("TLS dial should fail") + } + }) + + t.Run("with the certificate trustes as root certificate", func(t *testing.T) { + if err := dialTestServer(port, cert); err != nil { + t.Errorf("TLS dial should succeed, got error: %v", err) + } + }) + + t.Run("with a new certificate", func(t *testing.T) { + newCert := fakeCertificate(t, fs) + t.Run("when the certificate is not reloaded", func(t *testing.T) { + if dialTestServer(port, newCert) == nil { + t.Errorf("TLS dial should fail") + } + }) + // simulate watch.NewFileWatcher to call the load function + watcher.load() + t.Run("when the certificate is reloaded", func(t *testing.T) { + if err := dialTestServer(port, newCert); err != nil { + t.Errorf("TLS dial should succeed, got error: %v", err) + } + }) + }) + +} diff --git a/mkdocs.yml b/mkdocs.yml index 579303c12a..d682f33133 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,7 @@ nav: - Installation Guide: "deploy/index.md" - Bare-metal considerations: "deploy/baremetal.md" - Role Based Access Control (RBAC): "deploy/rbac.md" + - Validating Webhook (admission controller): "deploy/validating-webhook.md" - Upgrade: "deploy/upgrade.md" - User guide: - NGINX Configuration: diff --git a/vendor/k8s.io/api/admission/v1beta1/doc.go b/vendor/k8s.io/api/admission/v1beta1/doc.go new file mode 100644 index 0000000000..92f7c19d26 --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2017 The Kubernetes 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:protobuf-gen=package +// +k8s:openapi-gen=false + +// +groupName=admission.k8s.io + +package v1beta1 // import "k8s.io/api/admission/v1beta1" diff --git a/vendor/k8s.io/api/admission/v1beta1/generated.pb.go b/vendor/k8s.io/api/admission/v1beta1/generated.pb.go new file mode 100644 index 0000000000..4082082ff9 --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/generated.pb.go @@ -0,0 +1,1390 @@ +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/kubernetes/vendor/k8s.io/api/admission/v1beta1/generated.proto + +/* + Package v1beta1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/api/admission/v1beta1/generated.proto + + It has these top-level messages: + AdmissionRequest + AdmissionResponse + AdmissionReview +*/ +package v1beta1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import k8s_io_apimachinery_pkg_apis_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +import k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types" + +import github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *AdmissionRequest) Reset() { *m = AdmissionRequest{} } +func (*AdmissionRequest) ProtoMessage() {} +func (*AdmissionRequest) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *AdmissionResponse) Reset() { *m = AdmissionResponse{} } +func (*AdmissionResponse) ProtoMessage() {} +func (*AdmissionResponse) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + +func (m *AdmissionReview) Reset() { *m = AdmissionReview{} } +func (*AdmissionReview) ProtoMessage() {} +func (*AdmissionReview) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func init() { + proto.RegisterType((*AdmissionRequest)(nil), "k8s.io.api.admission.v1beta1.AdmissionRequest") + proto.RegisterType((*AdmissionResponse)(nil), "k8s.io.api.admission.v1beta1.AdmissionResponse") + proto.RegisterType((*AdmissionReview)(nil), "k8s.io.api.admission.v1beta1.AdmissionReview") +} +func (m *AdmissionRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AdmissionRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i += copy(dAtA[i:], m.UID) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Kind.Size())) + n1, err := m.Kind.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Resource.Size())) + n2, err := m.Resource.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SubResource))) + i += copy(dAtA[i:], m.SubResource) + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i += copy(dAtA[i:], m.Namespace) + dAtA[i] = 0x3a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) + i += copy(dAtA[i:], m.Operation) + dAtA[i] = 0x42 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.UserInfo.Size())) + n3, err := m.UserInfo.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + dAtA[i] = 0x4a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Object.Size())) + n4, err := m.Object.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.OldObject.Size())) + n5, err := m.OldObject.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + if m.DryRun != nil { + dAtA[i] = 0x58 + i++ + if *m.DryRun { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } + return i, nil +} + +func (m *AdmissionResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AdmissionResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i += copy(dAtA[i:], m.UID) + dAtA[i] = 0x10 + i++ + if m.Allowed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + if m.Result != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Result.Size())) + n6, err := m.Result.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.Patch != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Patch))) + i += copy(dAtA[i:], m.Patch) + } + if m.PatchType != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PatchType))) + i += copy(dAtA[i:], *m.PatchType) + } + if len(m.AuditAnnotations) > 0 { + keysForAuditAnnotations := make([]string, 0, len(m.AuditAnnotations)) + for k := range m.AuditAnnotations { + keysForAuditAnnotations = append(keysForAuditAnnotations, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForAuditAnnotations) + for _, k := range keysForAuditAnnotations { + dAtA[i] = 0x32 + i++ + v := m.AuditAnnotations[string(k)] + mapSize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + i = encodeVarintGenerated(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i += copy(dAtA[i:], v) + } + } + return i, nil +} + +func (m *AdmissionReview) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AdmissionReview) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Request != nil { + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Request.Size())) + n7, err := m.Request.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.Response != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Response.Size())) + n8, err := m.Response.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *AdmissionRequest) Size() (n int) { + var l int + _ = l + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Kind.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Resource.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.SubResource) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Namespace) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operation) + n += 1 + l + sovGenerated(uint64(l)) + l = m.UserInfo.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Object.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.OldObject.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.DryRun != nil { + n += 2 + } + return n +} + +func (m *AdmissionResponse) Size() (n int) { + var l int + _ = l + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + if m.Result != nil { + l = m.Result.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Patch != nil { + l = len(m.Patch) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.PatchType != nil { + l = len(*m.PatchType) + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.AuditAnnotations) > 0 { + for k, v := range m.AuditAnnotations { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + return n +} + +func (m *AdmissionReview) Size() (n int) { + var l int + _ = l + if m.Request != nil { + l = m.Request.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Response != nil { + l = m.Response.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func sovGenerated(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *AdmissionRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AdmissionRequest{`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `Kind:` + strings.Replace(strings.Replace(this.Kind.String(), "GroupVersionKind", "k8s_io_apimachinery_pkg_apis_meta_v1.GroupVersionKind", 1), `&`, ``, 1) + `,`, + `Resource:` + strings.Replace(strings.Replace(this.Resource.String(), "GroupVersionResource", "k8s_io_apimachinery_pkg_apis_meta_v1.GroupVersionResource", 1), `&`, ``, 1) + `,`, + `SubResource:` + fmt.Sprintf("%v", this.SubResource) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, + `UserInfo:` + strings.Replace(strings.Replace(this.UserInfo.String(), "UserInfo", "k8s_io_api_authentication_v1.UserInfo", 1), `&`, ``, 1) + `,`, + `Object:` + strings.Replace(strings.Replace(this.Object.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `OldObject:` + strings.Replace(strings.Replace(this.OldObject.String(), "RawExtension", "k8s_io_apimachinery_pkg_runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `DryRun:` + valueToStringGenerated(this.DryRun) + `,`, + `}`, + }, "") + return s +} +func (this *AdmissionResponse) String() string { + if this == nil { + return "nil" + } + keysForAuditAnnotations := make([]string, 0, len(this.AuditAnnotations)) + for k := range this.AuditAnnotations { + keysForAuditAnnotations = append(keysForAuditAnnotations, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForAuditAnnotations) + mapStringForAuditAnnotations := "map[string]string{" + for _, k := range keysForAuditAnnotations { + mapStringForAuditAnnotations += fmt.Sprintf("%v: %v,", k, this.AuditAnnotations[k]) + } + mapStringForAuditAnnotations += "}" + s := strings.Join([]string{`&AdmissionResponse{`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `Allowed:` + fmt.Sprintf("%v", this.Allowed) + `,`, + `Result:` + strings.Replace(fmt.Sprintf("%v", this.Result), "Status", "k8s_io_apimachinery_pkg_apis_meta_v1.Status", 1) + `,`, + `Patch:` + valueToStringGenerated(this.Patch) + `,`, + `PatchType:` + valueToStringGenerated(this.PatchType) + `,`, + `AuditAnnotations:` + mapStringForAuditAnnotations + `,`, + `}`, + }, "") + return s +} +func (this *AdmissionReview) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AdmissionReview{`, + `Request:` + strings.Replace(fmt.Sprintf("%v", this.Request), "AdmissionRequest", "AdmissionRequest", 1) + `,`, + `Response:` + strings.Replace(fmt.Sprintf("%v", this.Response), "AdmissionResponse", "AdmissionResponse", 1) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *AdmissionRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AdmissionRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AdmissionRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Kind.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Resource.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubResource", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SubResource = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Namespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operation = Operation(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.UserInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Object", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OldObject", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.OldObject.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.DryRun = &b + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AdmissionResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AdmissionResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AdmissionResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Allowed = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Result == nil { + m.Result = &k8s_io_apimachinery_pkg_apis_meta_v1.Status{} + } + if err := m.Result.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Patch", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Patch = append(m.Patch[:0], dAtA[iNdEx:postIndex]...) + if m.Patch == nil { + m.Patch = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PatchType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := PatchType(dAtA[iNdEx:postIndex]) + m.PatchType = &s + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuditAnnotations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AuditAnnotations == nil { + m.AuditAnnotations = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.AuditAnnotations[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AdmissionReview) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AdmissionReview: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AdmissionReview: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Request", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Request == nil { + m.Request = &AdmissionRequest{} + } + if err := m.Request.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Response", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Response == nil { + m.Response = &AdmissionResponse{} + } + if err := m.Response.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/api/admission/v1beta1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 821 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcf, 0x6f, 0xe3, 0x44, + 0x14, 0x8e, 0x37, 0x69, 0x12, 0x4f, 0x2a, 0x36, 0x3b, 0x80, 0x64, 0x45, 0xc8, 0x09, 0x3d, 0xa0, + 0x20, 0x6d, 0xc7, 0xb4, 0x82, 0x55, 0xb5, 0xe2, 0x12, 0xd3, 0x08, 0x55, 0x48, 0xdb, 0x6a, 0x76, + 0x83, 0x80, 0x03, 0xd2, 0xc4, 0x9e, 0x4d, 0x4c, 0xe2, 0x19, 0xe3, 0x99, 0x49, 0xc9, 0x0d, 0x71, + 0xe5, 0x82, 0xc4, 0x9f, 0xc4, 0xa5, 0xc7, 0x3d, 0xee, 0x29, 0xa2, 0xe1, 0xbf, 0xe8, 0x09, 0x79, + 0x3c, 0x8e, 0x43, 0xba, 0x85, 0x5d, 0xb4, 0x27, 0xfb, 0xfd, 0xf8, 0xbe, 0x37, 0xf3, 0xbd, 0x37, + 0x0f, 0x0c, 0x67, 0x27, 0x02, 0x45, 0xdc, 0x9b, 0xa9, 0x31, 0x4d, 0x19, 0x95, 0x54, 0x78, 0x0b, + 0xca, 0x42, 0x9e, 0x7a, 0x26, 0x40, 0x92, 0xc8, 0x23, 0x61, 0x1c, 0x09, 0x11, 0x71, 0xe6, 0x2d, + 0x8e, 0xc6, 0x54, 0x92, 0x23, 0x6f, 0x42, 0x19, 0x4d, 0x89, 0xa4, 0x21, 0x4a, 0x52, 0x2e, 0x39, + 0xfc, 0x20, 0xcf, 0x46, 0x24, 0x89, 0xd0, 0x26, 0x1b, 0x99, 0xec, 0xce, 0xe1, 0x24, 0x92, 0x53, + 0x35, 0x46, 0x01, 0x8f, 0xbd, 0x09, 0x9f, 0x70, 0x4f, 0x83, 0xc6, 0xea, 0xb9, 0xb6, 0xb4, 0xa1, + 0xff, 0x72, 0xb2, 0xce, 0xc3, 0xed, 0xd2, 0x4a, 0x4e, 0x29, 0x93, 0x51, 0x40, 0x64, 0x5e, 0x7f, + 0xb7, 0x74, 0xe7, 0xd3, 0x32, 0x3b, 0x26, 0xc1, 0x34, 0x62, 0x34, 0x5d, 0x7a, 0xc9, 0x6c, 0x92, + 0x39, 0x84, 0x17, 0x53, 0x49, 0x5e, 0x85, 0xf2, 0xee, 0x42, 0xa5, 0x8a, 0xc9, 0x28, 0xa6, 0xb7, + 0x00, 0x8f, 0xfe, 0x0b, 0x20, 0x82, 0x29, 0x8d, 0xc9, 0x2e, 0xee, 0xe0, 0xf7, 0x3a, 0x68, 0x0f, + 0x0a, 0x45, 0x30, 0xfd, 0x51, 0x51, 0x21, 0xa1, 0x0f, 0xaa, 0x2a, 0x0a, 0x1d, 0xab, 0x67, 0xf5, + 0x6d, 0xff, 0x93, 0xab, 0x55, 0xb7, 0xb2, 0x5e, 0x75, 0xab, 0xa3, 0xb3, 0xd3, 0x9b, 0x55, 0xf7, + 0xc3, 0xbb, 0x0a, 0xc9, 0x65, 0x42, 0x05, 0x1a, 0x9d, 0x9d, 0xe2, 0x0c, 0x0c, 0xbf, 0x01, 0xb5, + 0x59, 0xc4, 0x42, 0xe7, 0x5e, 0xcf, 0xea, 0xb7, 0x8e, 0x1f, 0xa1, 0xb2, 0x03, 0x1b, 0x18, 0x4a, + 0x66, 0x93, 0xcc, 0x21, 0x50, 0x26, 0x03, 0x5a, 0x1c, 0xa1, 0x2f, 0x53, 0xae, 0x92, 0xaf, 0x69, + 0x9a, 0x1d, 0xe6, 0xab, 0x88, 0x85, 0xfe, 0xbe, 0x29, 0x5e, 0xcb, 0x2c, 0xac, 0x19, 0xe1, 0x14, + 0x34, 0x53, 0x2a, 0xb8, 0x4a, 0x03, 0xea, 0x54, 0x35, 0xfb, 0xe3, 0x37, 0x67, 0xc7, 0x86, 0xc1, + 0x6f, 0x9b, 0x0a, 0xcd, 0xc2, 0x83, 0x37, 0xec, 0xf0, 0x33, 0xd0, 0x12, 0x6a, 0x5c, 0x04, 0x9c, + 0x9a, 0xd6, 0xe3, 0x5d, 0x03, 0x68, 0x3d, 0x2d, 0x43, 0x78, 0x3b, 0x0f, 0xf6, 0x40, 0x8d, 0x91, + 0x98, 0x3a, 0x7b, 0x3a, 0x7f, 0x73, 0x85, 0x27, 0x24, 0xa6, 0x58, 0x47, 0xa0, 0x07, 0xec, 0xec, + 0x2b, 0x12, 0x12, 0x50, 0xa7, 0xae, 0xd3, 0x1e, 0x98, 0x34, 0xfb, 0x49, 0x11, 0xc0, 0x65, 0x0e, + 0xfc, 0x1c, 0xd8, 0x3c, 0xc9, 0x1a, 0x17, 0x71, 0xe6, 0x34, 0x34, 0xc0, 0x2d, 0x00, 0xe7, 0x45, + 0xe0, 0x66, 0xdb, 0xc0, 0x25, 0x00, 0x3e, 0x03, 0x4d, 0x25, 0x68, 0x7a, 0xc6, 0x9e, 0x73, 0xa7, + 0xa9, 0x15, 0xfb, 0x08, 0x6d, 0xbf, 0x88, 0x7f, 0x0c, 0x71, 0xa6, 0xd4, 0xc8, 0x64, 0x97, 0xea, + 0x14, 0x1e, 0xbc, 0x61, 0x82, 0x23, 0x50, 0xe7, 0xe3, 0x1f, 0x68, 0x20, 0x1d, 0x5b, 0x73, 0x1e, + 0xde, 0xd9, 0x05, 0x33, 0x83, 0x08, 0x93, 0xcb, 0xe1, 0x4f, 0x92, 0xb2, 0xac, 0x01, 0xfe, 0x3b, + 0x86, 0xba, 0x7e, 0xae, 0x49, 0xb0, 0x21, 0x83, 0xdf, 0x03, 0x9b, 0xcf, 0xc3, 0xdc, 0xe9, 0x80, + 0xff, 0xc3, 0xbc, 0x91, 0xf2, 0xbc, 0xe0, 0xc1, 0x25, 0x25, 0x3c, 0x00, 0xf5, 0x30, 0x5d, 0x62, + 0xc5, 0x9c, 0x56, 0xcf, 0xea, 0x37, 0x7d, 0x90, 0x9d, 0xe1, 0x54, 0x7b, 0xb0, 0x89, 0x1c, 0xfc, + 0x52, 0x03, 0x0f, 0xb6, 0x5e, 0x85, 0x48, 0x38, 0x13, 0xf4, 0xad, 0x3c, 0x8b, 0x8f, 0x41, 0x83, + 0xcc, 0xe7, 0xfc, 0x92, 0xe6, 0x2f, 0xa3, 0xe9, 0xdf, 0x37, 0x3c, 0x8d, 0x41, 0xee, 0xc6, 0x45, + 0x1c, 0x5e, 0x80, 0xba, 0x90, 0x44, 0x2a, 0x61, 0xa6, 0xfc, 0xe1, 0xeb, 0x4d, 0xf9, 0x53, 0x8d, + 0xc9, 0xaf, 0x85, 0xa9, 0x50, 0x73, 0x89, 0x0d, 0x0f, 0xec, 0x82, 0xbd, 0x84, 0xc8, 0x60, 0xaa, + 0x27, 0x79, 0xdf, 0xb7, 0xd7, 0xab, 0xee, 0xde, 0x45, 0xe6, 0xc0, 0xb9, 0x1f, 0x9e, 0x00, 0x5b, + 0xff, 0x3c, 0x5b, 0x26, 0xc5, 0xf8, 0x76, 0x32, 0x21, 0x2f, 0x0a, 0xe7, 0xcd, 0xb6, 0x81, 0xcb, + 0x64, 0xf8, 0xab, 0x05, 0xda, 0x44, 0x85, 0x91, 0x1c, 0x30, 0xc6, 0xa5, 0x1e, 0x24, 0xe1, 0xd4, + 0x7b, 0xd5, 0x7e, 0xeb, 0x78, 0x88, 0xfe, 0x6d, 0xfb, 0xa2, 0x5b, 0x3a, 0xa3, 0xc1, 0x0e, 0xcf, + 0x90, 0xc9, 0x74, 0xe9, 0x3b, 0x46, 0xa8, 0xf6, 0x6e, 0x18, 0xdf, 0x2a, 0xdc, 0xf9, 0x02, 0xbc, + 0xff, 0x4a, 0x12, 0xd8, 0x06, 0xd5, 0x19, 0x5d, 0xe6, 0x2d, 0xc4, 0xd9, 0x2f, 0x7c, 0x0f, 0xec, + 0x2d, 0xc8, 0x5c, 0x51, 0xdd, 0x0e, 0x1b, 0xe7, 0xc6, 0xe3, 0x7b, 0x27, 0xd6, 0xc1, 0x1f, 0x16, + 0xb8, 0xbf, 0x75, 0xb8, 0x45, 0x44, 0x2f, 0xe1, 0x08, 0x34, 0xd2, 0x7c, 0x49, 0x6a, 0x8e, 0xd6, + 0x31, 0x7a, 0xed, 0xcb, 0x69, 0x94, 0xdf, 0xca, 0x5a, 0x6d, 0x0c, 0x5c, 0x70, 0xc1, 0x6f, 0xf5, + 0x4a, 0xd3, 0xb7, 0x37, 0x0b, 0xd3, 0x7b, 0x43, 0xd1, 0xfc, 0x7d, 0xb3, 0xc3, 0xb4, 0x85, 0x37, + 0x74, 0xfe, 0xe1, 0xd5, 0xb5, 0x5b, 0x79, 0x71, 0xed, 0x56, 0x5e, 0x5e, 0xbb, 0x95, 0x9f, 0xd7, + 0xae, 0x75, 0xb5, 0x76, 0xad, 0x17, 0x6b, 0xd7, 0x7a, 0xb9, 0x76, 0xad, 0x3f, 0xd7, 0xae, 0xf5, + 0xdb, 0x5f, 0x6e, 0xe5, 0xbb, 0x86, 0x21, 0xfe, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xf4, 0xc2, 0x6f, + 0x1b, 0x71, 0x07, 0x00, 0x00, +} diff --git a/vendor/k8s.io/api/admission/v1beta1/generated.proto b/vendor/k8s.io/api/admission/v1beta1/generated.proto new file mode 100644 index 0000000000..451d4c9ad7 --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/generated.proto @@ -0,0 +1,123 @@ +/* +Copyright The Kubernetes 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.api.admission.v1beta1; + +import "k8s.io/api/authentication/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1beta1"; + +// AdmissionRequest describes the admission.Attributes for the admission request. +message AdmissionRequest { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // otherwise identical (parallel requests, requests when earlier requests did not modify etc) + // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. + // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + optional string uid = 1; + + // Kind is the type of object being manipulated. For example: Pod + optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 2; + + // Resource is the name of the resource being requested. This is not the kind. For example: pods + optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 3; + + // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent + // resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while + // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on + // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource + // "binding", and kind "Binding". + // +optional + optional string subResource = 4; + + // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and + // rely on the server to generate the name. If that is the case, this method will return the empty string. + // +optional + optional string name = 5; + + // Namespace is the namespace associated with the request (if any). + // +optional + optional string namespace = 6; + + // Operation is the operation being performed + optional string operation = 7; + + // UserInfo is information about the requesting user + optional k8s.io.api.authentication.v1.UserInfo userInfo = 8; + + // Object is the object from the incoming request prior to default values being applied + // +optional + optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 9; + + // OldObject is the existing object. Only populated for UPDATE requests. + // +optional + optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 10; + + // DryRun indicates that modifications will definitely not be persisted for this request. + // Defaults to false. + // +optional + optional bool dryRun = 11; +} + +// AdmissionResponse describes an admission response. +message AdmissionResponse { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding AdmissionRequest. + optional string uid = 1; + + // Allowed indicates whether or not the admission request was permitted. + optional bool allowed = 2; + + // Result contains extra details into why an admission request was denied. + // This field IS NOT consulted in any way if "Allowed" is "true". + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.Status status = 3; + + // The patch body. Currently we only support "JSONPatch" which implements RFC 6902. + // +optional + optional bytes patch = 4; + + // The type of Patch. Currently we only allow "JSONPatch". + // +optional + optional string patchType = 5; + + // AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). + // MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controller will prefix the keys with + // admission webhook name (e.g. imagepolicy.example.com/error=image-blacklisted). AuditAnnotations will be provided by + // the admission webhook to add additional context to the audit log for this request. + // +optional + map auditAnnotations = 6; +} + +// AdmissionReview describes an admission review request/response. +message AdmissionReview { + // Request describes the attributes for the admission request. + // +optional + optional AdmissionRequest request = 1; + + // Response describes the attributes for the admission response. + // +optional + optional AdmissionResponse response = 2; +} + diff --git a/vendor/k8s.io/api/admission/v1beta1/register.go b/vendor/k8s.io/api/admission/v1beta1/register.go new file mode 100644 index 0000000000..78d21a0c8a --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/register.go @@ -0,0 +1,51 @@ +/* +Copyright 2017 The Kubernetes 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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name for this API. +const GroupName = "admission.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &AdmissionReview{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/vendor/k8s.io/api/admission/v1beta1/types.go b/vendor/k8s.io/api/admission/v1beta1/types.go new file mode 100644 index 0000000000..653e847107 --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/types.go @@ -0,0 +1,127 @@ +/* +Copyright 2017 The Kubernetes 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 v1beta1 + +import ( + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AdmissionReview describes an admission review request/response. +type AdmissionReview struct { + metav1.TypeMeta `json:",inline"` + // Request describes the attributes for the admission request. + // +optional + Request *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"` + // Response describes the attributes for the admission response. + // +optional + Response *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"` +} + +// AdmissionRequest describes the admission.Attributes for the admission request. +type AdmissionRequest struct { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // otherwise identical (parallel requests, requests when earlier requests did not modify etc) + // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. + // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` + // Kind is the type of object being manipulated. For example: Pod + Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"` + // Resource is the name of the resource being requested. This is not the kind. For example: pods + Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"` + // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent + // resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while + // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on + // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource + // "binding", and kind "Binding". + // +optional + SubResource string `json:"subResource,omitempty" protobuf:"bytes,4,opt,name=subResource"` + // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and + // rely on the server to generate the name. If that is the case, this method will return the empty string. + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"` + // Namespace is the namespace associated with the request (if any). + // +optional + Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"` + // Operation is the operation being performed + Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"` + // UserInfo is information about the requesting user + UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"` + // Object is the object from the incoming request prior to default values being applied + // +optional + Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,9,opt,name=object"` + // OldObject is the existing object. Only populated for UPDATE requests. + // +optional + OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,10,opt,name=oldObject"` + // DryRun indicates that modifications will definitely not be persisted for this request. + // Defaults to false. + // +optional + DryRun *bool `json:"dryRun,omitempty" protobuf:"varint,11,opt,name=dryRun"` +} + +// AdmissionResponse describes an admission response. +type AdmissionResponse struct { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding AdmissionRequest. + UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"` + + // Allowed indicates whether or not the admission request was permitted. + Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"` + + // Result contains extra details into why an admission request was denied. + // This field IS NOT consulted in any way if "Allowed" is "true". + // +optional + Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` + + // The patch body. Currently we only support "JSONPatch" which implements RFC 6902. + // +optional + Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"` + + // The type of Patch. Currently we only allow "JSONPatch". + // +optional + PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"` + + // AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). + // MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controller will prefix the keys with + // admission webhook name (e.g. imagepolicy.example.com/error=image-blacklisted). AuditAnnotations will be provided by + // the admission webhook to add additional context to the audit log for this request. + // +optional + AuditAnnotations map[string]string `json:"auditAnnotations,omitempty" protobuf:"bytes,6,opt,name=auditAnnotations"` +} + +// PatchType is the type of patch being used to represent the mutated object +type PatchType string + +// PatchType constants. +const ( + PatchTypeJSONPatch PatchType = "JSONPatch" +) + +// Operation is the type of resource operation being checked for admission control +type Operation string + +// Operation constants +const ( + Create Operation = "CREATE" + Update Operation = "UPDATE" + Delete Operation = "DELETE" + Connect Operation = "CONNECT" +) diff --git a/vendor/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go b/vendor/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go new file mode 100644 index 0000000000..8a938db3b4 --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go @@ -0,0 +1,73 @@ +/* +Copyright The Kubernetes 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 v1beta1 + +// This file contains a collection of methods that can be used from go-restful to +// generate Swagger API documentation for its models. Please read this PR for more +// information on the implementation: https://github.com/emicklei/go-restful/pull/215 +// +// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if +// they are on one line! For multiple line or blocks that you want to ignore use ---. +// Any context after a --- is ignored. +// +// Those methods can be generated by using hack/update-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. +var map_AdmissionRequest = map[string]string{ + "": "AdmissionRequest describes the admission.Attributes for the admission request.", + "uid": "UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are otherwise identical (parallel requests, requests when earlier requests did not modify etc) The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.", + "kind": "Kind is the type of object being manipulated. For example: Pod", + "resource": "Resource is the name of the resource being requested. This is not the kind. For example: pods", + "subResource": "SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. For instance, /pods has the resource \"pods\" and the kind \"Pod\", while /pods/foo/status has the resource \"pods\", the sub resource \"status\", and the kind \"Pod\" (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource \"pods\", subresource \"binding\", and kind \"Binding\".", + "name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this method will return the empty string.", + "namespace": "Namespace is the namespace associated with the request (if any).", + "operation": "Operation is the operation being performed", + "userInfo": "UserInfo is information about the requesting user", + "object": "Object is the object from the incoming request prior to default values being applied", + "oldObject": "OldObject is the existing object. Only populated for UPDATE requests.", + "dryRun": "DryRun indicates that modifications will definitely not be persisted for this request. Defaults to false.", +} + +func (AdmissionRequest) SwaggerDoc() map[string]string { + return map_AdmissionRequest +} + +var map_AdmissionResponse = map[string]string{ + "": "AdmissionResponse describes an admission response.", + "uid": "UID is an identifier for the individual request/response. This should be copied over from the corresponding AdmissionRequest.", + "allowed": "Allowed indicates whether or not the admission request was permitted.", + "status": "Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\".", + "patch": "The patch body. Currently we only support \"JSONPatch\" which implements RFC 6902.", + "patchType": "The type of Patch. Currently we only allow \"JSONPatch\".", + "auditAnnotations": "AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controller will prefix the keys with admission webhook name (e.g. imagepolicy.example.com/error=image-blacklisted). AuditAnnotations will be provided by the admission webhook to add additional context to the audit log for this request.", +} + +func (AdmissionResponse) SwaggerDoc() map[string]string { + return map_AdmissionResponse +} + +var map_AdmissionReview = map[string]string{ + "": "AdmissionReview describes an admission review request/response.", + "request": "Request describes the attributes for the admission request.", + "response": "Response describes the attributes for the admission response.", +} + +func (AdmissionReview) SwaggerDoc() map[string]string { + return map_AdmissionReview +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/vendor/k8s.io/api/admission/v1beta1/zz_generated.deepcopy.go b/vendor/k8s.io/api/admission/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..2b4352a948 --- /dev/null +++ b/vendor/k8s.io/api/admission/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,125 @@ +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1beta1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionRequest) DeepCopyInto(out *AdmissionRequest) { + *out = *in + out.Kind = in.Kind + out.Resource = in.Resource + in.UserInfo.DeepCopyInto(&out.UserInfo) + in.Object.DeepCopyInto(&out.Object) + in.OldObject.DeepCopyInto(&out.OldObject) + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequest. +func (in *AdmissionRequest) DeepCopy() *AdmissionRequest { + if in == nil { + return nil + } + out := new(AdmissionRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionResponse) DeepCopyInto(out *AdmissionResponse) { + *out = *in + if in.Result != nil { + in, out := &in.Result, &out.Result + *out = new(v1.Status) + (*in).DeepCopyInto(*out) + } + if in.Patch != nil { + in, out := &in.Patch, &out.Patch + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.PatchType != nil { + in, out := &in.PatchType, &out.PatchType + *out = new(PatchType) + **out = **in + } + if in.AuditAnnotations != nil { + in, out := &in.AuditAnnotations, &out.AuditAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionResponse. +func (in *AdmissionResponse) DeepCopy() *AdmissionResponse { + if in == nil { + return nil + } + out := new(AdmissionResponse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Request != nil { + in, out := &in.Request, &out.Request + *out = new(AdmissionRequest) + (*in).DeepCopyInto(*out) + } + if in.Response != nil { + in, out := &in.Response, &out.Response + *out = new(AdmissionResponse) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview. +func (in *AdmissionReview) DeepCopy() *AdmissionReview { + if in == nil { + return nil + } + out := new(AdmissionReview) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AdmissionReview) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +}