From d637a9b978af70c29c64f6703fba528c6b9593ba Mon Sep 17 00:00:00 2001 From: Jason Roberts Date: Sun, 3 Jun 2018 20:10:41 -0500 Subject: [PATCH] Configurable Proxy Protocol header timeout for TLS passthrough --- .../user-guide/nginx-configuration/configmap.md | 6 ++++++ internal/ingress/controller/config/config.go | 8 ++++++++ internal/ingress/controller/nginx.go | 3 ++- .../ingress/controller/template/configmap.go | 13 +++++++++++++ .../controller/template/configmap_test.go | 17 +++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md index e441f5eff..995c0b176 100644 --- a/docs/user-guide/nginx-configuration/configmap.md +++ b/docs/user-guide/nginx-configuration/configmap.md @@ -84,6 +84,7 @@ The following table shows a configuration option's name, type, and the default v |[ssl-session-timeout](#ssl-session-timeout)|string|"10m"| |[ssl-buffer-size](#ssl-buffer-size)|string|"4k"| |[use-proxy-protocol](#use-proxy-protocol)|bool|"false"| +|[proxy-protocol-header-timeout](#proxy-protocol-header-timeout)|string|"5s"| |[use-gzip](#use-gzip)|bool|"true"| |[use-geoip](#use-geoip)|bool|"true"| |[enable-brotli](#enable-brotli)|bool|"true"| @@ -479,6 +480,11 @@ _References:_ Enables or disables the [PROXY protocol](https://www.nginx.com/resources/admin-guide/proxy-protocol/) to receive client connection (real IP address) information passed through proxy servers and load balancers such as HAProxy and Amazon Elastic Load Balancer (ELB). +## proxy-protocol-header-timeout + +Sets the timeout value for receiving the proxy-protocol headers. The default of 5 seconds prevents the TLS passthrough handler from waiting indefinetly on a dropped connection. +_**default:**_ 5s + ## use-gzip Enables or disables compression of HTTP responses using the ["gzip" module](http://nginx.org/en/docs/http/ngx_http_gzip_module.html). diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go index 1744fec1d..bd119a4bc 100644 --- a/internal/ingress/controller/config/config.go +++ b/internal/ingress/controller/config/config.go @@ -20,6 +20,7 @@ import ( "fmt" "runtime" "strconv" + "time" "github.com/golang/glog" @@ -346,6 +347,11 @@ type Configuration struct { // https://www.nginx.com/resources/admin-guide/proxy-protocol/ UseProxyProtocol bool `json:"use-proxy-protocol,omitempty"` + // When use-proxy-protocol is enabled, sets the maximum time the connection handler will wait + // to receive proxy headers. + // Example '60s' + ProxyProtocolHeaderTimeout time.Duration `json:"proxy-protocol-header-timeout,omitempty"` + // Enables or disables the use of the nginx module that compresses responses using the "gzip" method // http://nginx.org/en/docs/http/ngx_http_gzip_module.html UseGzip bool `json:"use-gzip,omitempty"` @@ -528,6 +534,7 @@ func NewDefault() Configuration { defIPCIDR = append(defIPCIDR, "0.0.0.0/0") defNginxStatusIpv4Whitelist = append(defNginxStatusIpv4Whitelist, "127.0.0.1") defNginxStatusIpv6Whitelist = append(defNginxStatusIpv6Whitelist, "::1") + defProxyDeadlineDuration := time.Duration(5) * time.Second cfg := Configuration{ AllowBackendServerHeader: false, @@ -566,6 +573,7 @@ func NewDefault() Configuration { NginxStatusIpv4Whitelist: defNginxStatusIpv4Whitelist, NginxStatusIpv6Whitelist: defNginxStatusIpv6Whitelist, ProxyRealIPCIDR: defIPCIDR, + ProxyProtocolHeaderTimeout: defProxyDeadlineDuration, ServerNameHashMaxSize: 1024, ProxyHeadersHashMaxSize: 512, ProxyHeadersHashBucketSize: 64, diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go index 020909b71..d300629f8 100644 --- a/internal/ingress/controller/nginx.go +++ b/internal/ingress/controller/nginx.go @@ -704,6 +704,7 @@ func nextPowerOf2(v int) int { } func (n *NGINXController) setupSSLProxy() { + cfg := n.store.GetBackendConfiguration() sslPort := n.cfg.ListenPorts.HTTPS proxyPort := n.cfg.ListenPorts.SSLProxy @@ -722,7 +723,7 @@ func (n *NGINXController) setupSSLProxy() { glog.Fatalf("%v", err) } - proxyList := &proxyproto.Listener{Listener: listener} + proxyList := &proxyproto.Listener{Listener: listener, ProxyHeaderTimeout: cfg.ProxyProtocolHeaderTimeout} // start goroutine that accepts tcp connections in port 443 go func() { diff --git a/internal/ingress/controller/template/configmap.go b/internal/ingress/controller/template/configmap.go index 5ad40545c..8092ef5c0 100644 --- a/internal/ingress/controller/template/configmap.go +++ b/internal/ingress/controller/template/configmap.go @@ -21,6 +21,7 @@ import ( "net" "strconv" "strings" + "time" "github.com/golang/glog" @@ -42,6 +43,7 @@ const ( hideHeaders = "hide-headers" nginxStatusIpv4Whitelist = "nginx-status-ipv4-whitelist" nginxStatusIpv6Whitelist = "nginx-status-ipv6-whitelist" + proxyHeaderTimeout = "proxy-protocol-header-timeout" ) var ( @@ -125,6 +127,17 @@ func ReadConfig(src map[string]string) config.Configuration { } } + // Verify that the configured timeout is parsable as a duration. if not, set the default value + if val, ok := conf[proxyHeaderTimeout]; ok { + delete(conf, proxyHeaderTimeout) + duration, err := time.ParseDuration(val) + if err != nil { + glog.Warningf("proxy-protocol-header-timeout of %v encounted an error while being parsed %v. Switching to use default value instead.", val, err) + } else { + to.ProxyProtocolHeaderTimeout = duration + } + } + streamResponses := 1 if val, ok := conf[proxyStreamResponses]; ok { delete(conf, proxyStreamResponses) diff --git a/internal/ingress/controller/template/configmap_test.go b/internal/ingress/controller/template/configmap_test.go index ba05d2694..aab3eec78 100644 --- a/internal/ingress/controller/template/configmap_test.go +++ b/internal/ingress/controller/template/configmap_test.go @@ -18,6 +18,7 @@ package template import ( "testing" + "time" "github.com/kylelemons/godebug/pretty" @@ -31,6 +32,22 @@ func TestFilterErrors(t *testing.T) { } } +func TestProxytTimeoutParsing(t *testing.T) { + testCases := map[string]struct { + input string + expect time.Duration // duration in seconds + }{ + "valid duration": {"35s", time.Duration(35) * time.Second}, + "invalid duration": {"3zxs", time.Duration(5) * time.Second}, + } + for n, tc := range testCases { + cfg := ReadConfig(map[string]string{"proxy-protocol-header-timeout": tc.input}) + if cfg.ProxyProtocolHeaderTimeout.Seconds() != tc.expect.Seconds() { + t.Errorf("Testing %v. Expected %v seconds but got %v seconds", n, tc.expect, cfg.ProxyProtocolHeaderTimeout) + } + } +} + func TestMergeConfigMapToStruct(t *testing.T) { conf := map[string]string{ "custom-http-errors": "300,400,demo",