From 951a704cec152cec2814dd12c5914fc70e67538b Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Tue, 30 Jan 2018 12:29:03 +0800 Subject: [PATCH] Add connection-proxy-header annotation (#1999) This is the override the default connection header --- internal/ingress/annotations/annotations.go | 3 + .../ingress/annotations/connection/main.go | 72 +++++++++++++++++++ .../annotations/connection/main_test.go | 64 +++++++++++++++++ internal/ingress/controller/controller.go | 2 + internal/ingress/types.go | 5 ++ internal/ingress/types_equals.go | 3 + rootfs/etc/nginx/template/nginx.tmpl | 14 ++-- 7 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 internal/ingress/annotations/connection/main.go create mode 100644 internal/ingress/annotations/connection/main_test.go diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index b6064de6b0..2d56d4a878 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -28,6 +28,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/authreq" "k8s.io/ingress-nginx/internal/ingress/annotations/authtls" "k8s.io/ingress-nginx/internal/ingress/annotations/clientbodybuffersize" + "k8s.io/ingress-nginx/internal/ingress/annotations/connection" "k8s.io/ingress-nginx/internal/ingress/annotations/cors" "k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend" "k8s.io/ingress-nginx/internal/ingress/annotations/healthcheck" @@ -63,6 +64,7 @@ type Ingress struct { CertificateAuth authtls.Config ClientBodyBufferSize string ConfigurationSnippet string + Connection connection.Config CorsConfig cors.Config DefaultBackend string Denied error @@ -99,6 +101,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "CertificateAuth": authtls.NewParser(cfg), "ClientBodyBufferSize": clientbodybuffersize.NewParser(cfg), "ConfigurationSnippet": snippet.NewParser(cfg), + "Connection": connection.NewParser(cfg), "CorsConfig": cors.NewParser(cfg), "DefaultBackend": defaultbackend.NewParser(cfg), "ExternalAuth": authreq.NewParser(cfg), diff --git a/internal/ingress/annotations/connection/main.go b/internal/ingress/annotations/connection/main.go new file mode 100644 index 0000000000..78eae71ba4 --- /dev/null +++ b/internal/ingress/annotations/connection/main.go @@ -0,0 +1,72 @@ +/* +Copyright 2016 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 connection + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +// Config returns the connection header configuration for an Ingress rule +type Config struct { + Header string `json:"header"` + Enabled bool `json:"enabled"` +} + +type connection struct { + r resolver.Resolver +} + +// NewParser creates a new port in redirect annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return connection{r} +} + +// Parse parses the annotations contained in the ingress +// rule used to indicate if the connection header should be overridden. +func (a connection) Parse(ing *extensions.Ingress) (interface{}, error) { + cp, err := parser.GetStringAnnotation("connection-proxy-header", ing) + if err != nil { + return &Config{ + Enabled: false, + }, err + } + return &Config{ + Enabled: true, + Header: cp, + }, nil +} + +// Equal tests for equality between two Connection types +func (r1 *Config) Equal(r2 *Config) bool { + if r1 == r2 { + return true + } + if r1 == nil || r2 == nil { + return false + } + if r1.Enabled != r2.Enabled { + return false + } + if r1.Header != r2.Header { + return false + } + + return true +} diff --git a/internal/ingress/annotations/connection/main_test.go b/internal/ingress/annotations/connection/main_test.go new file mode 100644 index 0000000000..1900fc29da --- /dev/null +++ b/internal/ingress/annotations/connection/main_test.go @@ -0,0 +1,64 @@ +/* +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 connection + +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" +) + +func TestParse(t *testing.T) { + annotation := parser.GetAnnotationWithPrefix("connection-proxy-header") + + ap := NewParser(&resolver.Mock{}) + if ap == nil { + t.Fatalf("expected a parser.IngressAnnotation but returned nil") + } + + testCases := []struct { + annotations map[string]string + expected *Config + }{ + {map[string]string{annotation: ""}, &Config{Enabled: true, Header: ""}}, + {map[string]string{annotation: "keep-alive"}, &Config{Enabled: true, Header: "keep-alive"}}, + {map[string]string{}, &Config{Enabled: false}}, + {nil, &Config{Enabled: false}}, + } + + ing := &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{}, + } + + for _, testCase := range testCases { + ing.SetAnnotations(testCase.annotations) + i, _ := ap.Parse(ing) + p, _ := i.(*Config) + + if !p.Equal(testCase.expected) { + t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, p, testCase.annotations) + } + } +} diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index c115fa2da1..abc8079201 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -426,6 +426,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] loc.Denied = anns.Denied loc.XForwardedPrefix = anns.XForwardedPrefix loc.UsePortInRedirects = anns.UsePortInRedirects + loc.Connection = anns.Connection if loc.Redirect.FromToWWW { server.RedirectFromToWWW = true @@ -458,6 +459,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] Denied: anns.Denied, XForwardedPrefix: anns.XForwardedPrefix, UsePortInRedirects: anns.UsePortInRedirects, + Connection: anns.Connection, } if loc.Redirect.FromToWWW { diff --git a/internal/ingress/types.go b/internal/ingress/types.go index b58856d056..eec95179f0 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -26,6 +26,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/connection" "k8s.io/ingress-nginx/internal/ingress/annotations/cors" "k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist" "k8s.io/ingress-nginx/internal/ingress/annotations/proxy" @@ -240,6 +241,10 @@ type Location struct { // ConfigurationSnippet contains additional configuration for the backend // to be considered in the configuration of the location ConfigurationSnippet string `json:"configurationSnippet"` + // Connection contains connection header to orverride the default Connection header + // to the request. + // +optional + Connection connection.Config `json:"connection"` // ClientBodyBufferSize allows for the configuration of the client body // buffer size for a specific location. // +optional diff --git a/internal/ingress/types_equals.go b/internal/ingress/types_equals.go index a5902646a8..081542fe35 100644 --- a/internal/ingress/types_equals.go +++ b/internal/ingress/types_equals.go @@ -370,6 +370,9 @@ func (l1 *Location) Equal(l2 *Location) bool { if l1.XForwardedPrefix != l2.XForwardedPrefix { return false } + if !(&l1.Connection).Equal(&l2.Connection) { + return false + } return true } diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 7c8265d48f..15ec3e6274 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -119,7 +119,7 @@ http { include /etc/nginx/mime.types; default_type text/html; - + {{ if $cfg.EnableBrotli }} brotli on; brotli_comp_level {{ $cfg.BrotliLevel }}; @@ -242,7 +242,7 @@ http { port_in_redirect off; rewrite_log on; - + ssl_protocols {{ $cfg.SSLProtocols }}; # turn on session caching to drastically improve performance @@ -294,7 +294,7 @@ http { {{ range $header := $cfg.HideHeaders }}proxy_hide_header {{ $header }}; {{ end }} - + {{ if not (empty $cfg.HTTPSnippet) }} # Custom code snippet configured in the configuration configmap {{ $cfg.HTTPSnippet }} @@ -700,10 +700,10 @@ stream { {{/* redirect to HTTPS can be achieved forcing the redirect or having a SSL Certificate configured for the server */}} {{ if (or $location.Rewrite.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Rewrite.SSLRedirect)) }} # enforce ssl on server side - if ($redirect_to_https) { + if ($redirect_to_https) { {{ if $location.UsePortInRedirects }} # using custom ports require a different rewrite directive - {{ $redirect_port := (printf ":%v" $all.ListenPorts.HTTPS) }} + {{ $redirect_port := (printf ":%v" $all.ListenPorts.HTTPS) }} error_page 497 ={{ $all.Cfg.HTTPRedirectCode }} https://$host{{ $redirect_port }}$request_uri; return 497; @@ -798,7 +798,11 @@ stream { # Allow websocket connections proxy_set_header Upgrade $http_upgrade; + {{ if $location.Connection.Enabled}} + proxy_set_header Connection {{ $location.Connection.Header }}; + {{ else }} proxy_set_header Connection $connection_upgrade; + {{ end }} proxy_set_header X-Real-IP $the_real_ip; {{ if $all.Cfg.ComputeFullForwardedFor }}