diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index 0ca62be34..75e96b760 100644 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -27,6 +27,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz |[nginx.ingress.kubernetes.io/auth-tls-error-page](#client-certificate-authentication)|string| |[nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream](#client-certificate-authentication)|"true" or "false"| |[nginx.ingress.kubernetes.io/auth-url](#external-authentication)|string| +|[nginx.ingress.kubernetes.io/backend-protocol](#backend-protocol)|string|HTTP,HTTPS,GRPC,GRPCS,AJP| |[nginx.ingress.kubernetes.io/base-url-scheme](#rewrite)|string| |[nginx.ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string| |[nginx.ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string| @@ -382,7 +383,9 @@ The annotation `nginx.ingress.kubernetes.io/ssl-passthrough` allows to configure !!! attention The use of this annotation requires the flag `--enable-ssl-passthrough` (By default it is disabled). -### Secure backends +### Secure backends DEPRECATED (since 0.18.0) + +Please use `nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"` By default NGINX uses plain HTTP to reach the services. Adding the annotation `nginx.ingress.kubernetes.io/secure-backends: "true"` in the Ingress rule changes the protocol to HTTPS. @@ -569,7 +572,9 @@ nginx.ingress.kubernetes.io/lua-resty-waf-extra-rules: '[=[ { "access": [ { "act For details on how to write WAF rules, please refer to [https://github.com/p0pr0ck5/lua-resty-waf](https://github.com/p0pr0ck5/lua-resty-waf). -### gRPC backend +### gRPC backend DEPRECATED (since 0.18.0) + +Please use `nginx.ingress.kubernetes.io/backend-protocol: "GRPC"` or `nginx.ingress.kubernetes.io/backend-protocol: "GRPCS"` Since NGINX 1.13.10 it is possible to expose [gRPC services natively](http://nginx.org/en/docs/http/ngx_http_grpc_module.html) @@ -602,3 +607,16 @@ To use the module in the Kubernetes Nginx ingress controller, you have two optio - Use an InfluxDB server configured to enable the [UDP protocol](https://docs.influxdata.com/influxdb/v1.5/supported_protocols/udp/). - Deploy Telegraf as a sidecar proxy to the Ingress controller configured to listen UDP with the [socket listener input](https://github.com/influxdata/telegraf/tree/release-1.6/plugins/inputs/socket_listener) and to write using anyone of the [outputs plugins](https://github.com/influxdata/telegraf/tree/release-1.6/plugins/outputs) + +### Backend Protocol + +Using `backend-protocol` annotations is possible to indicate how NGINX should communicate with the backend service. +Valid Values: HTTP, HTTPS, GRPC, GRPCS and AJP + +By default NGINX uses `HTTP`. + +Example: + +```yaml +nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" +``` diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index edeea8c20..34c8a91b8 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -29,6 +29,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/auth" "k8s.io/ingress-nginx/internal/ingress/annotations/authreq" "k8s.io/ingress-nginx/internal/ingress/annotations/authtls" + "k8s.io/ingress-nginx/internal/ingress/annotations/backendprotocol" "k8s.io/ingress-nginx/internal/ingress/annotations/clientbodybuffersize" "k8s.io/ingress-nginx/internal/ingress/annotations/connection" "k8s.io/ingress-nginx/internal/ingress/annotations/cors" @@ -65,6 +66,7 @@ const DeniedKeyName = "Denied" // Ingress defines the valid annotations present in one NGINX Ingress rule type Ingress struct { metav1.ObjectMeta + BackendProtocol string Alias string BasicDigestAuth auth.Config CertificateAuth authtls.Config @@ -137,6 +139,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "GRPC": grpc.NewParser(cfg), "LuaRestyWAF": luarestywaf.NewParser(cfg), "InfluxDB": influxdb.NewParser(cfg), + "BackendProtocol": backendprotocol.NewParser(cfg), }, } } diff --git a/internal/ingress/annotations/backendprotocol/main.go b/internal/ingress/annotations/backendprotocol/main.go new file mode 100644 index 000000000..66dbae783 --- /dev/null +++ b/internal/ingress/annotations/backendprotocol/main.go @@ -0,0 +1,62 @@ +/* +Copyright 2018 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 backendprotocol + +import ( + "regexp" + "strings" + + "github.com/golang/glog" + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +var ( + validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS)$`) +) + +type backendProtocol struct { + r resolver.Resolver +} + +// NewParser creates a new backend protocol annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return backendProtocol{r} +} + +// ParseAnnotations parses the annotations contained in the ingress +// rule used to indicate the backend protocol. +func (a backendProtocol) Parse(ing *extensions.Ingress) (interface{}, error) { + if ing.GetAnnotations() == nil { + return "HTTP", nil + } + + proto, err := parser.GetStringAnnotation("backend-protocol", ing) + if err != nil { + return "HTTP", nil + } + + proto = strings.TrimSpace(strings.ToUpper(proto)) + if !validProtocols.MatchString(proto) { + glog.Warningf("Protocol %v is not a valid value for the backend-protocol annotation. Using HTTP as protocol", proto) + return "HTTP", nil + } + + return proto, nil +} diff --git a/internal/ingress/annotations/backendprotocol/main_test.go b/internal/ingress/annotations/backendprotocol/main_test.go new file mode 100644 index 000000000..50a7fbcdd --- /dev/null +++ b/internal/ingress/annotations/backendprotocol/main_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2018 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 backendprotocol + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" + + "k8s.io/apimachinery/pkg/util/intstr" +) + +func buildIngress() *extensions.Ingress { + return &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{ + Backend: &extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + }, + } +} + +func TestParseAnnotations(t *testing.T) { + ing := buildIngress() + + _, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("backend-protocol")] = "HTTPS" + ing.SetAnnotations(data) + i, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Errorf("unexpected error parsing ingress with backend-protocol") + } + val, ok := i.(string) + if !ok { + t.Errorf("expected a string type") + } + if val != "HTTPS" { + t.Errorf("expected HTTPS but %v returned", val) + } +} diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index ed7fd32d3..c0474b380 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -469,6 +469,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] loc.LuaRestyWAF = anns.LuaRestyWAF loc.InfluxDB = anns.InfluxDB loc.DefaultBackend = anns.DefaultBackend + loc.BackendProtocol = anns.BackendProtocol if loc.Redirect.FromToWWW { server.RedirectFromToWWW = true @@ -509,6 +510,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] LuaRestyWAF: anns.LuaRestyWAF, InfluxDB: anns.InfluxDB, DefaultBackend: anns.DefaultBackend, + BackendProtocol: anns.BackendProtocol, } if loc.Redirect.FromToWWW { diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index b17a731bc..4d2bf0026 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -403,12 +403,28 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur } path := location.Path - proto := "http" + proto := "http://" proxyPass := "proxy_pass" + + switch location.BackendProtocol { + case "HTTPS": + proto = "https://" + case "GRPC": + proto = "grpc://" + proxyPass = "grpc_pass" + case "GRPCS": + proto = "grpcs://" + proxyPass = "grpc_pass" + case "AJP": + proto = "" + proxyPass = "ajp_pass" + } + + // TODO: Remove after the deprecation of grpc-backend annotation if location.GRPC { proxyPass = "grpc_pass" - proto = "grpc" + proto = "grpc://" } upstreamName := "upstream_balancer" @@ -420,9 +436,11 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur for _, backend := range backends { if backend.Name == location.Backend { if backend.Secure || backend.SSLPassthrough { - proto = "https" + // TODO: Remove after the deprecation of secure-backend annotation + proto = "https://" + // TODO: Remove after the deprecation of grpc-backend annotation if location.GRPC { - proto = "grpcs" + proto = "grpcs://" } } @@ -435,7 +453,7 @@ func buildProxyPass(host string, b interface{}, loc interface{}, dynamicConfigur } // defProxyPass returns the default proxy_pass, just the name of the upstream - defProxyPass := fmt.Sprintf("%v %s://%s;", proxyPass, proto, upstreamName) + defProxyPass := fmt.Sprintf("%v %s%s;", proxyPass, proto, upstreamName) // if the path in the ingress rule is equals to the target: no special rewrite if path == location.Rewrite.Target { @@ -476,13 +494,13 @@ subs_filter '%v' '$1' ro; return fmt.Sprintf(` rewrite (?i)%s(.*) /$1 break; rewrite (?i)%s / break; -%v%v %s://%s; +%v%v %s%s; %v`, path, location.Path, xForwardedPrefix, proxyPass, proto, upstreamName, abu) } return fmt.Sprintf(` rewrite (?i)%s(.*) %s/$1 break; -%v%v %s://%s; +%v%v %s%s; %v`, path, location.Rewrite.Target, xForwardedPrefix, proxyPass, proto, upstreamName, abu) } @@ -752,8 +770,8 @@ func isValidClientBodyBufferSize(input interface{}) bool { if err != nil { sLowercase := strings.ToLower(s) - kCheck := strings.TrimSuffix(sLowercase, "k") - _, err := strconv.Atoi(kCheck) + check := strings.TrimSuffix(sLowercase, "k") + _, err := strconv.Atoi(check) if err == nil { return true } @@ -921,7 +939,7 @@ func proxySetHeader(loc interface{}) string { return "proxy_set_header" } - if location.GRPC { + if location.GRPC || location.BackendProtocol == "GRPC" || location.BackendProtocol == "GRPCS" { return "grpc_set_header" } diff --git a/internal/ingress/types.go b/internal/ingress/types.go index 558f0b493..57f9ac6b0 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -270,6 +270,9 @@ type Location struct { // InfluxDB allows to monitor the incoming request by sending them to an influxdb database // +optional InfluxDB influxdb.Config `json:"influxDB,omitempty"` + // BackendProtocol indicates which protocol should be used to communicate with the service + // By default this is HTTP + BackendProtocol string `json:"backend-protocol"` } // SSLPassthroughBackend describes a SSL upstream server configured diff --git a/test/e2e/annotations/grpc.go b/test/e2e/annotations/grpc.go index 860bfa418..96ddc6a77 100644 --- a/test/e2e/annotations/grpc.go +++ b/test/e2e/annotations/grpc.go @@ -38,7 +38,7 @@ var _ = framework.IngressNginxDescribe("Annotations - grpc", func() { host := "grpc" annotations := map[string]string{ - "nginx.ingress.kubernetes.io/grpc-backend": "true", + "nginx.ingress.kubernetes.io/backend-protocol": "GRPC", } ing, err := f.EnsureIngress(framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "fortune-teller", 50051, &annotations)) Expect(err).NotTo(HaveOccurred())