diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md index eec4ab782..2cec835f1 100755 --- a/docs/user-guide/nginx-configuration/configmap.md +++ b/docs/user-guide/nginx-configuration/configmap.md @@ -115,6 +115,7 @@ The following table shows a configuration option's name, type, and the default v |[proxy-stream-responses](#proxy-stream-responses)|int|1| |[bind-address](#bind-address)|[]string|""| |[use-forwarded-headers](#use-forwarded-headers)|bool|"false"| +|[enable-real-ip](#enable-real-ip)|bool|"false"| |[forwarded-for-header](#forwarded-for-header)|string|"X-Forwarded-For"| |[compute-full-forwarded-for](#compute-full-forwarded-for)|bool|"false"| |[proxy-add-original-uri-header](#proxy-add-original-uri-header)|bool|"false"| @@ -745,6 +746,10 @@ If true, NGINX passes the incoming `X-Forwarded-*` headers to upstreams. Use thi If false, NGINX ignores incoming `X-Forwarded-*` headers, filling them with the request information it sees. Use this option if NGINX is exposed directly to the internet, or it's behind a L3/packet-based load balancer that doesn't alter the source IP in the packets. +## enable-real-ip + +`enable-real-ip` enables the configuration of [http://nginx.org/en/docs/http/ngx_http_realip_module.html](http://nginx.org/en/docs/http/ngx_http_realip_module.html). Specific attributes of the module can be configured further by using `forwarded-for-header` and `proxy-real-ip-cidr` settings. + ## forwarded-for-header Sets the header field for identifying the originating IP address of a client. _**default:**_ X-Forwarded-For diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go index e089a1191..64b98ef09 100644 --- a/internal/ingress/controller/config/config.go +++ b/internal/ingress/controller/config/config.go @@ -479,6 +479,9 @@ type Configuration struct { // Sets whether to use incoming X-Forwarded headers. UseForwardedHeaders bool `json:"use-forwarded-headers"` + // Sets whether to enable the real ip module + EnableRealIp bool `json:"enable-real-ip"` + // Sets the header field for identifying the originating IP address of a client // Default is X-Forwarded-For ForwardedForHeader string `json:"forwarded-for-header,omitempty"` @@ -712,6 +715,7 @@ func NewDefault() Configuration { EnableUnderscoresInHeaders: false, ErrorLogLevel: errorLevel, UseForwardedHeaders: false, + EnableRealIp: false, ForwardedForHeader: "X-Forwarded-For", ComputeFullForwardedFor: false, ProxyAddOriginalURIHeader: false, diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 0b6240d1d..21b6ff24c 100755 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -119,7 +119,7 @@ http { {{/* Enable the real_ip module only if we use either X-Forwarded headers or Proxy Protocol. */}} {{/* we use the value of the real IP for the geo_ip module */}} - {{ if or $cfg.UseForwardedHeaders $cfg.UseProxyProtocol }} + {{ if or (or $cfg.UseForwardedHeaders $cfg.UseProxyProtocol) $cfg.EnableRealIp }} {{ if $cfg.UseProxyProtocol }} real_ip_header proxy_protocol; {{ else }} diff --git a/test/e2e/settings/enable_real_ip.go b/test/e2e/settings/enable_real_ip.go new file mode 100644 index 000000000..027f4c1f8 --- /dev/null +++ b/test/e2e/settings/enable_real_ip.go @@ -0,0 +1,113 @@ +/* +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 settings + +import ( + "fmt" + "net/http" + "strings" + + "github.com/onsi/ginkgo" + "github.com/stretchr/testify/assert" + + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.DescribeSetting("enable-real-ip", func() { + f := framework.NewDefaultFramework("enable-real-ip") + + setting := "enable-real-ip" + + ginkgo.BeforeEach(func() { + f.NewEchoDeployment() + f.UpdateNginxConfigMapData(setting, "false") + }) + + ginkgo.It("trusts X-Forwarded-For header only when setting is true", func() { + host := "forwarded-for-header" + + f.UpdateNginxConfigMapData(setting, "true") + + ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, nil) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "server_name "+host) && + !strings.Contains(server, "proxy_set_header X-Forwarded-Proto $full_x_forwarded_proto;") + }) + + ginkgo.By("ensuring single values are parsed correctly") + body := f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + WithHeader("X-Forwarded-Port", "1234"). + WithHeader("X-Forwarded-Proto", "myproto"). + WithHeader("X-Forwarded-For", "1.2.3.4"). + WithHeader("X-Forwarded-Host", "myhost"). + Expect(). + Status(http.StatusOK). + Body(). + Raw() + + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("host=myhost")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-host=myhost")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-proto=myproto")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-port=1234")) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("host=%s", host)) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-host=%s", host)) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-proto=http")) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-port=80")) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-for=1.2.3.4")) + }) + + ginkgo.It("should not trust X-Forwarded-For header when setting is false", func() { + host := "forwarded-for-header" + + f.UpdateNginxConfigMapData(setting, "false") + + f.EnsureIngress(framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, nil)) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "server_name "+host) && + strings.Contains(server, "proxy_set_header X-Forwarded-Proto $pass_access_scheme;") + }) + + body := f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + WithHeader("X-Forwarded-Port", "1234"). + WithHeader("X-Forwarded-Proto", "myproto"). + WithHeader("X-Forwarded-For", "1.2.3.4"). + WithHeader("X-Forwarded-Host", "myhost"). + Expect(). + Status(http.StatusOK). + Body(). + Raw() + + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("host=%s", host)) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-port=80")) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-proto=http")) + assert.Contains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-original-forwarded-for=1.2.3.4")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("host=myhost")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-host=myhost")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-proto=myproto")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-port=1234")) + assert.NotContains(ginkgo.GinkgoT(), body, fmt.Sprintf("x-forwarded-for=1.2.3.4")) + }) +})