diff --git a/docs/configmap-and-annotations.md b/docs/configmap-and-annotations.md index 3d291add9b..92e9971ff8 100644 --- a/docs/configmap-and-annotations.md +++ b/docs/configmap-and-annotations.md @@ -1,6 +1,6 @@ # ConfigMap and Annotations -The Ingress resource only allows you to use basic NGINX features -- host and path-based routing and TLS termination. Thus, advanced features like rewriting the request URI or inserting additional response headers are not available. +The Ingress resource only allows you to use basic NGINX features -- host and path-based routing and TLS termination. Thus, advanced features like rewriting the request URI or inserting additional response headers are not available. In addition to using advanced features, often it is necessary to customize or fine tune NGINX behavior. For example, set the number of worker processes or customize the access log format. @@ -49,7 +49,7 @@ metadata: nginx.org/client-max-body-size: "4m" nginx.org/server-snippets: | location / { - return 302 /coffee; + return 302 /coffee; } spec: rules: @@ -102,7 +102,7 @@ spec: | N/A | `server-names-hash-bucket-size` | Sets the value of the [server_names_hash_bucket_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_bucket_size) directive. | Depends on the size of the processor’s cache line. | | | N/A | `server-names-hash-max-size` | Sets the value of the [server_names_hash_max_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_max_size) directive. | `512` | | -### Logging +### Logging | Annotation | ConfigMap Key | Description | Default | Example | | ---------- | -------------- | ----------- | ------- | ------- | @@ -127,6 +127,7 @@ spec: | `nginx.org/hsts` | `hsts` | Enables [HTTP Strict Transport Security (HSTS)](https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/): the HSTS header is added to the responses from backends. The `preload` directive is included in the header. | `False` | | | `nginx.org/hsts-max-age` | `hsts-max-age` | Sets the value of the `max-age` directive of the HSTS header. | `2592000` (1 month) | | `nginx.org/hsts-include-subdomains` | `hsts-include-subdomains` | Adds the `includeSubDomains` directive to the HSTS header. | `False`| | +| `nginx.org/hsts-behind-proxy` | `hsts-behind-proxy` | Enables HSTS based on the value of the `http_x_forwarded_proto` request header. Should only be used when TLS termination is configured in a load balancer (proxy) in front of the Ingress Controller. Note: to control redirection from HTTP to HTTPS configure the `nginx.org/redirect-to-https` annotation. | `False` | | | N/A | `ssl-protocols` | Sets the value of the [ssl_protocols](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols) directive. | `TLSv1 TLSv1.1 TLSv1.2`| | | N/A | `ssl-prefer-server-ciphers` | Enables or disables the [ssl_prefer_server_ciphers](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_prefer_server_ciphers) directive. | `False`| | | N/A | `ssl-ciphers` | Sets the value of the [ssl_ciphers](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers) directive. | `HIGH:!aNULL:!MD5`| | diff --git a/internal/nginx/config.go b/internal/nginx/config.go index e554fdcbf4..de3a1b1fa1 100644 --- a/internal/nginx/config.go +++ b/internal/nginx/config.go @@ -34,6 +34,7 @@ type Config struct { ProxyHideHeaders []string ProxyPassHeaders []string HSTS bool + HSTSBehindProxy bool HSTSMaxAge int64 HSTSIncludeSubdomains bool LBMethod string @@ -198,6 +199,11 @@ func ParseConfigMap(cfgm *api_v1.ConfigMap, nginxPlus bool) *Config { glog.Error(err) parsingErrors = true } + hstsBehindProxy, existsBP, err := GetMapKeyAsBool(cfgm.Data, "hsts-behind-proxy", cfgm) + if existsBP && err != nil { + glog.Error(err) + parsingErrors = true + } if parsingErrors { glog.Errorf("Configmap %s/%s: There are configuration issues with hsts annotations, skipping options for all hsts settings", cfgm.GetNamespace(), cfgm.GetName()) @@ -209,6 +215,9 @@ func ParseConfigMap(cfgm *api_v1.ConfigMap, nginxPlus bool) *Config { if existsIS { cfg.HSTSIncludeSubdomains = hstsIncludeSubdomains } + if existsBP { + cfg.HSTSBehindProxy = hstsBehindProxy + } } } } diff --git a/internal/nginx/configurator.go b/internal/nginx/configurator.go index e4a1501208..0e5337e85a 100644 --- a/internal/nginx/configurator.go +++ b/internal/nginx/configurator.go @@ -266,6 +266,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri HSTS: ingCfg.HSTS, HSTSMaxAge: ingCfg.HSTSMaxAge, HSTSIncludeSubdomains: ingCfg.HSTSIncludeSubdomains, + HSTSBehindProxy: ingCfg.HSTSBehindProxy, StatusZone: statuzZone, RealIPHeader: ingCfg.RealIPHeader, SetRealIPFrom: ingCfg.SetRealIPFrom, @@ -553,6 +554,11 @@ func (cnf *Configurator) createConfig(ingEx *IngressEx) Config { glog.Error(err) parsingErrors = true } + hstsBehindProxy, existsBP, err := GetMapKeyAsBool(ingEx.Ingress.Annotations, "nginx.org/hsts-behind-proxy", ingEx.Ingress) + if existsBP && err != nil { + glog.Error(err) + parsingErrors = true + } if parsingErrors { glog.Errorf("Ingress %s/%s: There are configuration issues with hsts annotations, skipping annotions for all hsts settings", ingEx.Ingress.GetNamespace(), ingEx.Ingress.GetName()) @@ -564,6 +570,9 @@ func (cnf *Configurator) createConfig(ingEx *IngressEx) Config { if existsIS { ingCfg.HSTSIncludeSubdomains = hstsIncludeSubdomains } + if existsBP { + ingCfg.HSTSBehindProxy = hstsBehindProxy + } } } } diff --git a/internal/nginx/nginx.go b/internal/nginx/nginx.go index b8cf48659f..b4001555a0 100644 --- a/internal/nginx/nginx.go +++ b/internal/nginx/nginx.go @@ -95,6 +95,7 @@ type Server struct { HSTS bool HSTSMaxAge int64 HSTSIncludeSubdomains bool + HSTSBehindProxy bool ProxyHideHeaders []string ProxyPassHeaders []string diff --git a/internal/nginx/templates/nginx-plus.ingress.tmpl b/internal/nginx/templates/nginx-plus.ingress.tmpl index 04b430b174..f1ba59bbeb 100644 --- a/internal/nginx/templates/nginx-plus.ingress.tmpl +++ b/internal/nginx/templates/nginx-plus.ingress.tmpl @@ -55,15 +55,28 @@ server { {{if $server.SSL}} {{if not $server.GRPCOnly}} + {{- if $server.HSTS}} + set $hsts_header_val ""; + proxy_hide_header Strict-Transport-Security; + {{- if $server.HSTSBehindProxy}} + if ($http_x_forwarded_proto = 'https') { + {{else}} + if ($https = on) { + {{- end}} + set $hsts_header_val "max-age={{$server.HSTSMaxAge}}; {{if $server.HSTSIncludeSubdomains}}includeSubDomains; {{end}}preload"; + } + + add_header Strict-Transport-Security "$hsts_header_val" always; + {{end}} + {{- if $server.SSLRedirect}} if ($scheme = http) { return 301 https://$host:{{index $server.SSLPorts 0}}$request_uri; } {{- end}} - {{- if $server.HSTS}} - add_header Strict-Transport-Security "max-age={{$server.HSTSMaxAge}}; {{if $server.HSTSIncludeSubdomains}}includeSubDomains; {{end}}preload" always;{{end}} {{end}} {{- end}} + {{- if $server.RedirectToHTTPS}} if ($http_x_forwarded_proto = 'http') { return 301 https://$host$request_uri; diff --git a/internal/nginx/templates/nginx.ingress.tmpl b/internal/nginx/templates/nginx.ingress.tmpl index cf0ebffae8..6a6aec19ec 100644 --- a/internal/nginx/templates/nginx.ingress.tmpl +++ b/internal/nginx/templates/nginx.ingress.tmpl @@ -37,18 +37,31 @@ server { proxy_hide_header {{$proxyHideHeader}};{{end}} {{range $proxyPassHeader := $server.ProxyPassHeaders}} proxy_pass_header {{$proxyPassHeader}};{{end}} + {{if $server.SSL}} {{if not $server.GRPCOnly}} + {{- if $server.HSTS}} + set $hsts_header_val ""; + proxy_hide_header Strict-Transport-Security; + {{- if $server.HSTSBehindProxy}} + if ($http_x_forwarded_proto = 'https') { + {{else}} + if ($https = on) { + {{- end}} + set $hsts_header_val "max-age={{$server.HSTSMaxAge}}; {{if $server.HSTSIncludeSubdomains}}includeSubDomains; {{end}}preload"; + } + + add_header Strict-Transport-Security "$hsts_header_val" always; + {{end}} + {{- if $server.SSLRedirect}} if ($scheme = http) { return 301 https://$host:{{index $server.SSLPorts 0}}$request_uri; } {{- end}} - {{- if $server.HSTS}} - proxy_hide_header Strict-Transport-Security; - add_header Strict-Transport-Security "max-age={{$server.HSTSMaxAge}}; {{if $server.HSTSIncludeSubdomains}}includeSubDomains; {{end}}preload" always;{{end}} {{end}} {{- end}} + {{- if $server.RedirectToHTTPS}} if ($http_x_forwarded_proto = 'http') { return 301 https://$host$request_uri; @@ -66,7 +79,7 @@ server { # location for minion {{$location.MinionIngress.Namespace}}/{{$location.MinionIngress.Name}} {{end}} {{if $location.GRPC}} - {{if not $server.GRPCOnly}} + {{if not $server.GRPCOnly}} error_page 400 @grpcerror400; error_page 401 @grpcerror401; error_page 403 @grpcerror403; @@ -81,7 +94,7 @@ server { error_page 503 @grpcerror503; error_page 504 @grpcerror504; {{end}} - + {{- if $location.LocationSnippets}} {{range $value := $location.LocationSnippets}} {{$value}}{{end}} diff --git a/nginx-controller/nginx-controller b/nginx-controller/nginx-controller new file mode 100755 index 0000000000..3d43880a69 Binary files /dev/null and b/nginx-controller/nginx-controller differ