Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix source IP address #1485

Merged
merged 1 commit into from
Oct 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions controllers/nginx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This is an nginx Ingress controller that uses [ConfigMap](https://kubernetes.io/
* [HTTPS enforcement](#server-side-https-enforcement)
* [HSTS](#http-strict-transport-security)
* [Kube-Lego](#automated-certificate-management-with-kube-lego)
* [Source IP address](#source-ip-address)
* [TCP Services](#exposing-tcp-services)
* [UDP Services](#exposing-udp-services)
* [Proxy Protocol](#proxy-protocol)
Expand Down Expand Up @@ -333,6 +334,13 @@ version to fully support Kube-Lego is nginx Ingress controller 0.8.
[Kube-Lego]:https://github.com/jetstack/kube-lego
[Let's Encrypt]:https://letsencrypt.org

## Source IP address

By default NGINX uses the content of the header `X-Forwarded-For` as the source of truth to get information about the client IP address. This works without issues in L7 **if we configure the setting `proxy-real-ip-cidr`** with the correct information of the IP/network address of the external load balancer.
If the ingress controller is running in AWS we need to use the VPC IPv4 CIDR. This allows NGINX to avoid the spoofing of the header.
Another option is to enable proxy protocol using `use-proxy-protocol: "true"`.
In this mode NGINX do not uses the content of the header to get the source IP address of the connection.

## Exposing TCP services

Ingress does not support TCP services (yet). For this reason this Ingress controller uses the flag `--tcp-services-configmap` to point to an existing config map where the key is the external port to use and the value is `<namespace/service name>:<service port>:[PROXY]:[PROXY]`
Expand Down
6 changes: 0 additions & 6 deletions controllers/nginx/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,6 @@ type Configuration struct {
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_headers_hash_bucket_size
ProxyHeadersHashBucketSize int `json:"proxy-headers-hash-bucket-size,omitempty"`

// RealClientFrom defines the trusted source of the client source IP address
// The valid values are "auto", "http-proxy" and "tcp-proxy"
// Default: auto
RealClientFrom string `json:"real-client-from,omitempty"`

// Enables or disables emitting nginx version in error messages and in the “Server” response header field.
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens
// Default: true
Expand Down Expand Up @@ -479,7 +474,6 @@ func NewDefault() Configuration {
LimitConnZoneVariable: defaultLimitConnZoneVariable,
BindAddressIpv4: defBindAddress,
BindAddressIpv6: defBindAddress,
RealClientFrom: "auto",
ZipkinCollectorPort: 9411,
ZipkinServiceName: "nginx",
}
Expand Down
10 changes: 0 additions & 10 deletions controllers/nginx/pkg/template/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package template
import (
"fmt"
"net"
"regexp"
"strconv"
"strings"

Expand All @@ -39,10 +38,6 @@ const (
bindAddress = "bind-address"
)

var (
realClientRegex = regexp.MustCompile(`auto|http-proxy|tcp-proxy`)
)

// ReadConfig obtains the configuration defined by the user merged with the defaults.
func ReadConfig(src map[string]string) config.Configuration {
conf := map[string]string{}
Expand Down Expand Up @@ -125,11 +120,6 @@ func ReadConfig(src map[string]string) config.Configuration {
glog.Warningf("unexpected error merging defaults: %v", err)
}

if !realClientRegex.MatchString(to.RealClientFrom) {
glog.Warningf("unexpected value for RealClientFromSetting (%v). Using default \"auto\"", to.RealClientFrom)
to.RealClientFrom = "auto"
}

return to
}

Expand Down
28 changes: 2 additions & 26 deletions controllers/nginx/pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,6 @@ var (
},
"isValidClientBodyBufferSize": isValidClientBodyBufferSize,
"buildForwardedFor": buildForwardedFor,
"trustHTTPHeaders": trustHTTPHeaders,
"trustProxyProtocol": trustProxyProtocol,
"buildAuthSignURL": buildAuthSignURL,
}
)
Expand Down Expand Up @@ -671,28 +669,6 @@ func buildForwardedFor(input interface{}) string {
return fmt.Sprintf("$http_%v", ffh)
}

func trustHTTPHeaders(input interface{}) bool {
conf, ok := input.(config.TemplateConfig)
if !ok {
glog.Errorf("%v", input)
return true
}

return conf.Cfg.RealClientFrom == "http-proxy" ||
(conf.Cfg.RealClientFrom == "auto" && !conf.Cfg.UseProxyProtocol)
}

func trustProxyProtocol(input interface{}) bool {
conf, ok := input.(config.TemplateConfig)
if !ok {
glog.Errorf("%v", input)
return true
}

return conf.Cfg.RealClientFrom == "tcp-proxy" ||
(conf.Cfg.RealClientFrom == "auto" && conf.Cfg.UseProxyProtocol)
}

func buildAuthSignURL(input interface{}) string {
s, ok := input.(string)
if !ok {
Expand All @@ -703,12 +679,12 @@ func buildAuthSignURL(input interface{}) string {
u, _ := url.Parse(s)
q := u.Query()
if len(q) == 0 {
return fmt.Sprintf("%v?rd=$request_uri", s)
return fmt.Sprintf("%v?rd=$scheme://$http_host$request_uri", s)
}

if q.Get("rd") != "" {
return s
}

return fmt.Sprintf("%v&rd=$request_uri", s)
return fmt.Sprintf("%v&rd=$scheme://$http_host$request_uri", s)
}
4 changes: 2 additions & 2 deletions controllers/nginx/pkg/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,8 @@ func TestBuildAuthSignURL(t *testing.T) {
cases := map[string]struct {
Input, Output string
}{
"default url": {"http://google.com", "http://google.com?rd=$request_uri"},
"with random field": {"http://google.com?cat=0", "http://google.com?cat=0&rd=$request_uri"},
"default url": {"http://google.com", "http://google.com?rd=$scheme://$http_host$request_uri"},
"with random field": {"http://google.com?cat=0", "http://google.com?cat=0&rd=$scheme://$http_host$request_uri"},
"with rd field": {"http://google.com?cat&rd=$request", "http://google.com?cat&rd=$request"},
}
for k, tc := range cases {
Expand Down
55 changes: 10 additions & 45 deletions controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,13 @@ http {
'' close;
}

{{ if (trustHTTPHeaders $all) }}
# Trust HTTP X-Forwarded-* Headers, but use direct values if they're missing.
map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip {
# Get IP address from X-Forwarded-For HTTP header
default $realip_remote_addr;
'' $remote_addr;
{{ if $cfg.UseProxyProtocol }}
# Get IP address from Proxy Protocol
default $proxy_protocol_addr;
{{ else }}
default $remote_addr;
{{ end }}
}

# trust http_x_forwarded_proto headers correctly indicate ssl offloading
Expand All @@ -175,30 +176,6 @@ http {
'' $this_host;
}

{{ else }}
# Do not trust HTTP X-Forwarded-* Headers
map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip {
{{ if (trustProxyProtocol $all) }}
# Get IP address from Proxy Protocol
default $proxy_protocol_addr;
{{ else }}
# Get IP from direct remote address
default $realip_remote_addr;
{{ end }}
}

map $http_x_forwarded_host $best_http_host {
default $this_host;
}
map $http_x_forwarded_proto $pass_access_scheme {
default $scheme;
}
map $http_x_forwarded_port $pass_server_port {
default $server_port;
}

{{ end }}

{{ if $all.IsSSLPassthroughEnabled }}
# map port {{ $all.ListenPorts.SSLProxy }} to 443 for header X-Forwarded-Port
map $pass_server_port $pass_port {
Expand All @@ -212,21 +189,6 @@ http {
}
{{ end }}

# Map a response error watching the header Content-Type
map $http_accept $httpAccept {
default html;
application/json json;
application/xml xml;
text/plain text;
}

map $httpAccept $httpReturnType {
default text/html;
json application/json;
xml application/xml;
text text/plain;
}

# Obtain best http host
map $http_host $this_host {
default $http_host;
Expand Down Expand Up @@ -688,8 +650,8 @@ stream {

{{ end }}

location {{ $path }} {

location {{ $path }} {
{{ if $all.Cfg.EnableVtsStatus }}{{ if $location.VtsFilterKey }} vhost_traffic_status_filter_by_set_key {{ $location.VtsFilterKey }};{{ end }}{{ end }}

set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $all.Backends $location }}";
Expand Down Expand Up @@ -786,6 +748,9 @@ stream {
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Scheme $pass_access_scheme;

# Pass the original X-Forwarded-For
proxy_set_header X-Original-Forwarded-For {{ buildForwardedFor $all.Cfg.ForwardedForHeader }};

# mitigate HTTPoxy Vulnerability
# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
proxy_set_header Proxy "";
Expand Down