diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 7a70fdc6b..4ab05b447 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -468,6 +468,8 @@ The default mime type list to compress is: `application/atom+xml application/jav **add-headers:** Sets custom headers from a configmap before sending traffic to the client. See `proxy-set-headers` [example](https://github.com/kubernetes/ingress/tree/master/examples/customization/custom-headers/nginx) +**bind-address:** Sets the addresses on which the server will accept requests instead of *. It should be noted that these addresses must exist in the runtime environment or the controller will crash loop. + ### Default configuration options The following table shows the options, the default value and a description. @@ -521,6 +523,7 @@ The following table shows the options, the default value and a description. |whitelist-source-range|permit all| |worker-processes|number of CPUs| |limit-conn-zone-variable|$binary_remote_addr| +|bind-address|| ### Websockets diff --git a/controllers/nginx/pkg/config/config.go b/controllers/nginx/pkg/config/config.go index a48a21550..8b02483ac 100644 --- a/controllers/nginx/pkg/config/config.go +++ b/controllers/nginx/pkg/config/config.go @@ -342,12 +342,19 @@ type Configuration struct { // If no data is transmitted within this time, the connection is closed. // http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_timeout ProxyStreamTimeout string `json:"proxy-stream-timeout,omitempty"` + + // Sets the ipv4 addresses on which the server will accept requests. + BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"` + + // Sets the ipv6 addresses on which the server will accept requests. + BindAddressIpv6 []string `json:"bind-address-ipv6,omitempty"` } // NewDefault returns the default nginx configuration func NewDefault() Configuration { defIPCIDR := make([]string, 0) defIPCIDR = append(defIPCIDR, "0.0.0.0/0") + defBindAddress := make([]string, 0) cfg := Configuration{ AllowBackendServerHeader: false, AccessLogPath: "/var/log/nginx/access.log", @@ -414,6 +421,8 @@ func NewDefault() Configuration { }, UpstreamKeepaliveConnections: 0, LimitConnZoneVariable: defaultLimitConnZoneVariable, + BindAddressIpv4: defBindAddress, + BindAddressIpv6: defBindAddress, } if glog.V(5) { diff --git a/controllers/nginx/pkg/template/configmap.go b/controllers/nginx/pkg/template/configmap.go index 2b5401e42..7113aef4e 100644 --- a/controllers/nginx/pkg/template/configmap.go +++ b/controllers/nginx/pkg/template/configmap.go @@ -17,6 +17,8 @@ limitations under the License. package template import ( + "fmt" + "net" "strconv" "strings" @@ -24,6 +26,7 @@ import ( "github.com/mitchellh/mapstructure" "k8s.io/ingress/controllers/nginx/pkg/config" + ing_net "k8s.io/ingress/core/pkg/net" ) const ( @@ -31,6 +34,7 @@ const ( skipAccessLogUrls = "skip-access-log-urls" whitelistSourceRange = "whitelist-source-range" proxyRealIPCIDR = "proxy-real-ip-cidr" + bindAddress = "bind-address" ) // ReadConfig obtains the configuration defined by the user merged with the defaults. @@ -47,6 +51,8 @@ func ReadConfig(src map[string]string) config.Configuration { skipUrls := make([]string, 0) whitelist := make([]string, 0) proxylist := make([]string, 0) + bindAddressIpv4List := make([]string, 0) + bindAddressIpv6List := make([]string, 0) if val, ok := conf[customHTTPErrors]; ok { delete(conf, customHTTPErrors) @@ -73,12 +79,29 @@ func ReadConfig(src map[string]string) config.Configuration { } else { proxylist = append(proxylist, "0.0.0.0/0") } + if val, ok := conf[bindAddress]; ok { + delete(conf, bindAddress) + for _, i := range strings.Split(val, ",") { + ns := net.ParseIP(i) + if ns != nil { + if ing_net.IsIPV6(ns) { + bindAddressIpv6List = append(bindAddressIpv6List, fmt.Sprintf("[%v]", ns)) + } else { + bindAddressIpv4List = append(bindAddressIpv4List, fmt.Sprintf("%v", ns)) + } + } else { + glog.Warningf("%v is not a valid textual representation of an IP address", i) + } + } + } to := config.NewDefault() to.CustomHTTPErrors = filterErrors(errors) to.SkipAccessLogURLs = skipUrls to.WhitelistSourceRange = whitelist to.ProxyRealIPCIDR = proxylist + to.BindAddressIpv4 = bindAddressIpv4List + to.BindAddressIpv6 = bindAddressIpv6List config := &mapstructure.DecoderConfig{ Metadata: nil, diff --git a/controllers/nginx/pkg/template/configmap_test.go b/controllers/nginx/pkg/template/configmap_test.go index 828d98f37..6561974e1 100644 --- a/controllers/nginx/pkg/template/configmap_test.go +++ b/controllers/nginx/pkg/template/configmap_test.go @@ -45,6 +45,7 @@ func TestMergeConfigMapToStruct(t *testing.T) { "enable-dynamic-tls-records": "false", "gzip-types": "text/html", "proxy-real-ip-cidr": "1.1.1.1/8,2.2.2.2/24", + "bind-address": "1.1.1.1,2.2.2.2,3.3.3,2001:db8:a0b:12f0::1,3731:54:65fe:2::a7,33:33:33::33::33", } def := config.NewDefault() def.CustomHTTPErrors = []int{300, 400} @@ -58,6 +59,8 @@ func TestMergeConfigMapToStruct(t *testing.T) { def.UseProxyProtocol = true def.GzipTypes = "text/html" def.ProxyRealIPCIDR = []string{"1.1.1.1/8", "2.2.2.2/24"} + def.BindAddressIpv4 = []string{"1.1.1.1", "2.2.2.2"} + def.BindAddressIpv6 = []string{"[2001:db8:a0b:12f0::1]", "[3731:54:65fe:2::a7]"} to := ReadConfig(conf) if diff := pretty.Compare(to, def); diff != "" { diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index bcd171091..cebc7041d 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -319,12 +319,22 @@ http { {{/* Build server redirects (from/to www) */}} {{ range $hostname, $to := .RedirectServers }} server { + {{ range $address := $all.Cfg.BindAddressIpv4 }} + listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; + listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl; + {{ else }} listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl; + {{ end }} {{ if $IsIPV6Enabled }} + {{ range $address := $all.Cfg.BindAddressIpv6 }} + listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; + listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}; + {{ else }} listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}; {{ end }} + {{ end }} server_name {{ $hostname }}; return 301 $scheme://{{ $to }}$request_uri; } @@ -350,8 +360,8 @@ http { # Use the port {{ $all.ListenPorts.Status }} (random value just to avoid known ports) as default port for nginx. # Changing this value requires a change in: # https://github.com/kubernetes/ingress/blob/master/controllers/nginx/pkg/cmd/controller/nginx.go - listen {{ $all.ListenPorts.Status }} default_server reuseport backlog={{ .BacklogSize }}; - {{ if $IsIPV6Enabled }}listen [::]:{{ $all.ListenPorts.Status }} default_server reuseport backlog={{ .BacklogSize }};{{ end }} + listen 127.0.0.1:{{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }}; + {{ if $IsIPV6Enabled }}listen [::1]:{{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }};{{ end }} set $proxy_upstream_name "-"; location {{ $healthzURI }} { @@ -394,7 +404,7 @@ http { # default server for services without endpoints server { - listen {{ $all.ListenPorts.Default }}; + listen 127.0.0.1:{{ $all.ListenPorts.Default }}; set $proxy_upstream_name "-"; location / { @@ -430,8 +440,18 @@ stream { {{ end }} } server { + {{ range $address := $all.Cfg.BindAddressIpv4 }} + listen {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.UseProxyProtocol }} proxy_protocol{{ end }}; + {{ else }} listen {{ $tcpServer.Port }}{{ if $tcpServer.Backend.UseProxyProtocol }} proxy_protocol{{ end }}; - {{ if $IsIPV6Enabled }}listen [::]:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.UseProxyProtocol }} proxy_protocol{{ end }};{{ end }} + {{ end }} + {{ if $IsIPV6Enabled }} + {{ range $address := $all.Cfg.BindAddressIpv6 }} + listen {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.UseProxyProtocol }} proxy_protocol{{ end }}; + {{ else }} + listen [::]:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.UseProxyProtocol }} proxy_protocol{{ end }}; + {{ end }} + {{ end }} proxy_timeout {{ $cfg.ProxyStreamTimeout }}; proxy_pass tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }}; } @@ -447,8 +467,18 @@ stream { } server { + {{ range $address := $all.Cfg.BindAddressIpv4 }} + listen {{ $address }}:{{ $udpServer.Port }} udp; + {{ else }} listen {{ $udpServer.Port }} udp; - {{ if $IsIPV6Enabled }}listen [::]:{{ $udpServer.Port }} udp;{{ end }} + {{ end }} + {{ if $IsIPV6Enabled }} + {{ range $address := $all.Cfg.BindAddressIpv6 }} + listen {{ $address }}:{{ $udpServer.Port }} udp; + {{ else }} + listen [::]:{{ $udpServer.Port }} udp; + {{ end }} + {{ end }} proxy_responses 1; proxy_timeout {{ $cfg.ProxyStreamTimeout }}; proxy_pass udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }}; @@ -518,14 +548,35 @@ stream { {{ define "SERVER" }} {{ $all := .First }} {{ $server := .Second }} + {{ range $address := $all.Cfg.BindAddressIpv4 }} + listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; + {{ else }} listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; - {{ if $all.IsIPV6Enabled }}listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }};{{ end }} + {{ end }} + {{ if $all.IsIPV6Enabled }} + {{ range $address := $all.Cfg.BindAddressIpv6 }} + listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }}; + {{ else }} + listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }}; + {{ end }} + {{ end }} set $proxy_upstream_name "-"; {{/* Listen on {{ $all.ListenPorts.SSLProxy }} because port {{ $all.ListenPorts.HTTPS }} is used in the TLS sni server */}} {{/* This listener must always have proxy_protocol enabled, because the SNI listener forwards on source IP info in it. */}} - {{ if not (empty $server.SSLCertificate) }}listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ if $all.IsIPV6Enabled }}{{ if not (empty $server.SSLCertificate) }}listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};{{ end }} + {{ if not (empty $server.SSLCertificate) }} + {{ range $address := $all.Cfg.BindAddressIpv4 }} + listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; + {{ else }} + listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; + {{ end }} + {{ if $all.IsIPV6Enabled }} + {{ range $address := $all.Cfg.BindAddressIpv6 }} + {{ if not (empty $server.SSLCertificate) }}listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; + {{ else }} + {{ if not (empty $server.SSLCertificate) }}listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; + {{ end }} + {{ end }} {{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}} # PEM sha: {{ $server.SSLPemChecksum }} ssl_certificate {{ $server.SSLCertificate }};