From ac504bdbc0018d6c338194fac933c624f5c7ba47 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Date: Wed, 9 Aug 2017 22:22:54 -0500 Subject: [PATCH 1/3] Add support for Server Alias in Nginx Adds support for server alias in nginx. Adds a new annotation which allows us to specify a server alias that will be appended to the server name. --- controllers/nginx/configuration.md | 15 ++++- controllers/nginx/pkg/cmd/controller/nginx.go | 1 + .../rootfs/etc/nginx/template/nginx.tmpl | 2 +- core/pkg/ingress/annotations/alias/main.go | 42 +++++++++++++ .../ingress/annotations/alias/main_test.go | 60 +++++++++++++++++++ core/pkg/ingress/controller/annotations.go | 8 +++ core/pkg/ingress/controller/controller.go | 20 ++++++- core/pkg/ingress/types.go | 2 + 8 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 core/pkg/ingress/annotations/alias/main.go create mode 100644 core/pkg/ingress/annotations/alias/main_test.go diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 101f0ee2d..888856448 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -64,7 +64,7 @@ The following annotations are supported: |[ingress.kubernetes.io/upstream-max-fails](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/upstream-fail-timeout](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR| - +|[ingress.kubernetes.io/server-alias](#server-alias)|string| #### Custom NGINX template @@ -155,7 +155,7 @@ Please check the [tls-auth](/examples/auth/client-certs/nginx/README.md) example ### Configuration snippet -Using this annotion you can add additional configuration to the NGINX location. For example: +Using this annotation you can add additional configuration to the NGINX location. For example: ``` ingress.kubernetes.io/configuration-snippet: | @@ -167,6 +167,17 @@ ingress.kubernetes.io/configuration-snippet: | To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule add the annotation `ingress.kubernetes.io/enable-cors: "true"`. This will add a section in the server location enabling this functionality. For more information please check https://enable-cors.org/server_nginx.html +### Server Alias + +To add Server Aliases to an Ingress rule add the annotation `ingress.kubernetes.io/server-alias: ":;...;:"`. +This will append a server-alias to the end of the server_name in the NGINX server. A server-alias can accept wildcards, but +it cannot accept port numbers. + +The server-name must match a valid server within the ingress resource for it to append the server-alias. Multiple server-aliases +can be added for multiple server-names using `;` as a delimiter. + +For more information please see http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name + ### External Authentication To use an existing service that provides authentication the Ingress rule can be annotated with `ingress.kubernetes.io/auth-url` to indicate the URL where the HTTP request should be sent. diff --git a/controllers/nginx/pkg/cmd/controller/nginx.go b/controllers/nginx/pkg/cmd/controller/nginx.go index 9f36d770c..990a86901 100644 --- a/controllers/nginx/pkg/cmd/controller/nginx.go +++ b/controllers/nginx/pkg/cmd/controller/nginx.go @@ -452,6 +452,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { IP: svc.Spec.ClusterIP, Port: port, ProxyProtocol: false, + }) } diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index a3953e38a..4ee4e7155 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -295,7 +295,7 @@ http { {{ $backlogSize := .BacklogSize }} {{ range $index, $server := .Servers }} server { - server_name {{ $server.Hostname }}; + server_name {{ $server.Hostname }} {{ $server.Alias }}; listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{end}}; {{ if $IsIPV6Enabled }}listen [::]:80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{ end }};{{ end }} set $proxy_upstream_name "-"; diff --git a/core/pkg/ingress/annotations/alias/main.go b/core/pkg/ingress/annotations/alias/main.go new file mode 100644 index 000000000..1e56d15f3 --- /dev/null +++ b/core/pkg/ingress/annotations/alias/main.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 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 alias + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress/core/pkg/ingress/annotations/parser" +) + +const ( + annotation = "ingress.kubernetes.io/server-alias" +) + +type alias struct { +} + +// NewParser creates a new CORS annotation parser +func NewParser() parser.IngressAnnotation { + return alias{} +} + +// Parse parses the annotations contained in the ingress rule +// used to indicate if the location/s contains a fragment of +// configuration to be included inside the paths of the rules +func (a alias) Parse(ing *extensions.Ingress) (interface{}, error) { + return parser.GetStringAnnotation(annotation, ing) +} \ No newline at end of file diff --git a/core/pkg/ingress/annotations/alias/main_test.go b/core/pkg/ingress/annotations/alias/main_test.go new file mode 100644 index 000000000..de4fe17f5 --- /dev/null +++ b/core/pkg/ingress/annotations/alias/main_test.go @@ -0,0 +1,60 @@ +/* +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 alias + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestParse(t *testing.T) { + ap := NewParser() + if ap == nil { + t.Fatalf("expected a parser.IngressAnnotation but returned nil") + } + + testCases := []struct { + annotations map[string]string + expected string + }{ + {map[string]string{annotation: "www.example.com"}, "www.example.com"}, + {map[string]string{annotation: "*.example.com www.example.*"}, "*.example.com www.example.*"}, + {map[string]string{annotation: `~^www\d+\.example\.com$`}, `~^www\d+\.example\.com$`}, + {map[string]string{annotation: ""}, ""}, + {map[string]string{}, ""}, + {nil, ""}, + } + + ing := &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{}, + } + + for _, testCase := range testCases { + ing.SetAnnotations(testCase.annotations) + result, _ := ap.Parse(ing) + if result != testCase.expected { + t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) + } + } +} diff --git a/core/pkg/ingress/controller/annotations.go b/core/pkg/ingress/controller/annotations.go index 7aadb9ddd..a122b2bf7 100644 --- a/core/pkg/ingress/controller/annotations.go +++ b/core/pkg/ingress/controller/annotations.go @@ -37,6 +37,7 @@ import ( "k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough" "k8s.io/ingress/core/pkg/ingress/errors" "k8s.io/ingress/core/pkg/ingress/resolver" + "k8s.io/ingress/core/pkg/ingress/annotations/alias" ) type extractorConfig interface { @@ -69,6 +70,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { "SessionAffinity": sessionaffinity.NewParser(), "SSLPassthrough": sslpassthrough.NewParser(), "ConfigurationSnippet": snippet.NewParser(), + "Alias": alias.NewParser(), }, } } @@ -107,6 +109,7 @@ const ( sslPassthrough = "SSLPassthrough" sessionAffinity = "SessionAffinity" serviceUpstream = "ServiceUpstream" + serverAlias = "Alias" ) func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool { @@ -133,6 +136,11 @@ func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool { return val.(bool) } +func (e *annotationExtractor) Alias(ing *extensions.Ingress) string { + val, _ := e.annotations[serverAlias].Parse(ing) + return val.(string) +} + func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig { val, _ := e.annotations[sessionAffinity].Parse(ing) return val.(*sessionaffinity.AffinityConfig) diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index 1181c3b2d..c7f6444f4 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -1055,19 +1055,37 @@ func (ic *GenericController) createServers(data []interface{}, } } - // configure default location and SSL + // configure default location, alias, and SSL for _, ingIf := range data { ing := ingIf.(*extensions.Ingress) if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) { continue } + // setup server-aliases based on annotations + aliasMap := map[string]string{} + aliasAnnotation := ic.annotations.Alias(ing) + + // Here we parse the annotation string in the following format: + // ingress.kubernetes.io/server-alias: "host_0:alias_0;...;host_n:alias_n" + aliases := strings.Split(aliasAnnotation, ";") + for _, alias := range aliases { + aliasParts := strings.Split(alias, ":") + if len(aliasParts) == 2 { + // aliasMap[host] = alias + aliasMap[aliasParts[0]] = aliasParts[1] + } + } + for _, rule := range ing.Spec.Rules { host := rule.Host if host == "" { host = defServerName } + // setup server aliases + servers[host].Alias = aliasMap[host] + // only add a certificate if the server does not have one previously configured if len(ing.Spec.TLS) == 0 || servers[host].SSLCertificate != "" { continue diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 28a30aa07..51e12592e 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -220,6 +220,8 @@ type Server struct { SSLPemChecksum string `json:"sslPemChecksum"` // Locations list of URIs configured in the server. Locations []*Location `json:"locations,omitempty"` + // return the alias of the server name + Alias string `json:"alias,omitempty"` } // Location describes an URI inside a server. From 62fea9aa014404a7ebd05f5ff3892422dd96dd8f Mon Sep 17 00:00:00 2001 From: Fernando Diaz Date: Tue, 15 Aug 2017 01:23:19 -0500 Subject: [PATCH 2/3] Update Server Alias Annotation with Review Changes Updates the Server-Alias annotation to create another server containing the same configuration as the current server, but with the name provided in the annotation. --- controllers/nginx/configuration.md | 8 +- controllers/nginx/pkg/template/template.go | 3 + .../rootfs/etc/nginx/template/nginx.tmpl | 412 +++++++++--------- core/pkg/ingress/controller/controller.go | 16 +- 4 files changed, 220 insertions(+), 219 deletions(-) diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 888856448..e4975cca6 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -169,12 +169,8 @@ For more information please check https://enable-cors.org/server_nginx.html ### Server Alias -To add Server Aliases to an Ingress rule add the annotation `ingress.kubernetes.io/server-alias: ":;...;:"`. -This will append a server-alias to the end of the server_name in the NGINX server. A server-alias can accept wildcards, but -it cannot accept port numbers. - -The server-name must match a valid server within the ingress resource for it to append the server-alias. Multiple server-aliases -can be added for multiple server-names using `;` as a delimiter. +To add Server Aliases to an Ingress rule add the annotation `ingress.kubernetes.io/server-alias: ""`. +This will create a server with the same configuration, but a different server_name as the provided host. For more information please see http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name diff --git a/controllers/nginx/pkg/template/template.go b/controllers/nginx/pkg/template/template.go index 607fb6abc..605434e03 100644 --- a/controllers/nginx/pkg/template/template.go +++ b/controllers/nginx/pkg/template/template.go @@ -147,6 +147,9 @@ var ( "toLower": strings.ToLower, "formatIP": formatIP, "buildNextUpstream": buildNextUpstream, + "serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} { + return struct { First, Second interface{} } { all, server } + }, } ) diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 4ee4e7155..4b0eada94 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -295,207 +295,17 @@ http { {{ $backlogSize := .BacklogSize }} {{ range $index, $server := .Servers }} server { - server_name {{ $server.Hostname }} {{ $server.Alias }}; - listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{end}}; - {{ if $IsIPV6Enabled }}listen [::]:80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{ end }};{{ end }} - set $proxy_upstream_name "-"; - - {{/* Listen on 442 because port 443 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 442 proxy_protocol{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{end}} ssl {{ if $cfg.UseHTTP2 }}http2{{ end }}; - {{ if $IsIPV6Enabled }}{{ if not (empty $server.SSLCertificate) }}listen [::]:442 proxy_protocol{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{end}} ssl {{ if $cfg.UseHTTP2 }}http2{{ 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 }}; - ssl_certificate_key {{ $server.SSLCertificate }}; - {{ end }} - - {{ if (and (not (empty $server.SSLCertificate)) $cfg.HSTS) }} - more_set_headers "Strict-Transport-Security: max-age={{ $cfg.HSTSMaxAge }}{{ if $cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }};{{ if $cfg.HSTSPreload }} preload{{ end }}"; - {{ end }} - - {{ if $cfg.EnableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }} - - {{ range $location := $server.Locations }} - {{ $path := buildLocation $location }} - {{ $authPath := buildAuthLocation $location }} - - {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }} - # PEM sha: {{ $location.CertificateAuth.AuthSSLCert.PemSHA }} - ssl_client_certificate {{ $location.CertificateAuth.AuthSSLCert.CAFileName }}; - ssl_verify_client on; - ssl_verify_depth {{ $location.CertificateAuth.ValidationDepth }}; - {{ end }} - - {{ if not (empty $location.Redirect.AppRoot)}} - if ($uri = /) { - return 302 {{ $location.Redirect.AppRoot }}; - } - {{ end }} - - {{ if not (empty $authPath) }} - location = {{ $authPath }} { - internal; - set $proxy_upstream_name "internal"; - - {{ if not $location.ExternalAuth.SendBody }} - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - {{ end }} - {{ if not (empty $location.ExternalAuth.Method) }} - proxy_method {{ $location.ExternalAuth.Method }}; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Scheme $pass_access_scheme; - {{ end }} - proxy_pass_request_headers on; - proxy_set_header Host {{ $location.ExternalAuth.Host }}; - proxy_ssl_server_name on; - - client_max_body_size "{{ $location.Proxy.BodySize }}"; - - - set $target {{ $location.ExternalAuth.URL }}; - proxy_pass $target; - } - {{ end }} - - location {{ $path }} { - set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $backends $location }}"; - - {{ if (or $location.Redirect.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect)) }} - # enforce ssl on server side - if ($pass_access_scheme = http) { - return 301 https://$best_http_host$request_uri; - } - {{ end }} - - {{ if isLocationAllowed $location }} - {{ if gt (len $location.Whitelist.CIDR) 0 }} - if ({{ buildDenyVariable (print $server.Hostname "_" $path) }}) { - return 403; - } - {{ end }} - - port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }}; - - {{ if not (empty $authPath) }} - # this location requires authentication - auth_request {{ $authPath }}; - {{- range $idx, $line := buildAuthResponseHeaders $location }} - {{ $line }} - {{- end }} - {{ end }} - - {{ if not (empty $location.ExternalAuth.SigninURL) }} - error_page 401 = {{ $location.ExternalAuth.SigninURL }}; - {{ end }} - - - {{/* if the location contains a rate limit annotation, create one */}} - {{ $limits := buildRateLimit $location }} - {{ range $limit := $limits }} - {{ $limit }}{{ end }} - - {{ if $location.BasicDigestAuth.Secured }} - {{ if eq $location.BasicDigestAuth.Type "basic" }} - auth_basic "{{ $location.BasicDigestAuth.Realm }}"; - auth_basic_user_file {{ $location.BasicDigestAuth.File }}; - {{ else }} - auth_digest "{{ $location.BasicDigestAuth.Realm }}"; - auth_digest_user_file {{ $location.BasicDigestAuth.File }}; - {{ end }} - proxy_set_header Authorization ""; - {{ end }} - - {{ if $location.EnableCORS }} - {{ template "CORS" }} - {{ end }} - - client_max_body_size "{{ $location.Proxy.BodySize }}"; - - proxy_set_header Host $best_http_host; - - # Pass the extracted client certificate to the backend - {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }} - proxy_set_header ssl-client-cert $ssl_client_cert; - {{ end }} - - # Allow websocket connections - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_set_header X-Real-IP $the_real_ip; - proxy_set_header X-Forwarded-For $the_real_ip; - proxy_set_header X-Forwarded-Host $best_http_host; - proxy_set_header X-Forwarded-Port $pass_port; - proxy_set_header X-Forwarded-Proto $pass_access_scheme; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Scheme $pass_access_scheme; - - # mitigate HTTPoxy Vulnerability - # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ - proxy_set_header Proxy ""; - - # Custom headers to proxied server - {{ range $k, $v := $proxyHeaders }} - proxy_set_header {{ $k }} "{{ $v }}"; - {{ end }} - - proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s; - proxy_send_timeout {{ $location.Proxy.SendTimeout }}s; - proxy_read_timeout {{ $location.Proxy.ReadTimeout }}s; - - proxy_redirect off; - proxy_buffering off; - proxy_buffer_size "{{ $location.Proxy.BufferSize }}"; - proxy_buffers 4 "{{ $location.Proxy.BufferSize }}"; - - proxy_http_version 1.1; - - proxy_cookie_domain {{ $location.Proxy.CookieDomain }}; - proxy_cookie_path {{ $location.Proxy.CookiePath }}; - - # In case of errors try the next upstream server before returning an error - proxy_next_upstream {{ buildNextUpstream $location.Proxy.NextUpstream }}{{ if $cfg.RetryNonIdempotent }} non_idempotent{{ end }}; - - {{/* rewrite only works if the content is not compressed */}} - {{ if $location.Redirect.AddBaseURL }} - proxy_set_header Accept-Encoding ""; - {{ end }} - - {{/* Add any additional configuration defined */}} - {{ $location.ConfigurationSnippet }} - - {{ buildProxyPass $server.Hostname $backends $location }} - {{ else }} - #{{ $location.Denied }} - return 503; - {{ end }} - } - {{ end }} - - {{ if eq $server.Hostname "_" }} - # health checks in cloud providers require the use of port 80 - location {{ $healthzURI }} { - access_log off; - return 200; - } - - # this is required to avoid error if nginx is being monitored - # with an external software (like sysdig) - location /nginx_status { - allow 127.0.0.1; - {{ if $IsIPV6Enabled }}allow ::1;{{ end }} - deny all; - - access_log off; - stub_status on; - } - {{ end }} - + server_name {{ $server.Hostname }}; + {{ template "SERVER" serverConfig $all $server }} {{ template "CUSTOM_ERRORS" $all }} } - + {{if $server.Alias }} + server { + server_name {{ $server.Alias }}; + {{ template "SERVER" serverConfig $all $server }} + {{ template "CUSTOM_ERRORS" $all }} + } + {{ end }} {{ end }} # default server, used for NGINX healthcheck and access to nginx stats @@ -666,3 +476,207 @@ stream { add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; } {{ end }} + +{{/* definition of server-template to avoid repetitions with server-alias */}} +{{ define "SERVER" }} + {{ $all := .First }} + {{ $server := .Second }} + listen 80{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; + {{ if $all.IsIPV6Enabled }}listen [::]:80{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }};{{ end }} + set $proxy_upstream_name "-"; + + {{/* Listen on 442 because port 443 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 442 proxy_protocol{{ 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 [::]:442 proxy_protocol{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ 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 }}; + ssl_certificate_key {{ $server.SSLCertificate }}; + {{ end }} + + {{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }} + more_set_headers "Strict-Transport-Security: max-age={{ $all.Cfg.HSTSMaxAge }}{{ if $all.Cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }};{{ if $all.Cfg.HSTSPreload }} preload{{ end }}"; + {{ end }} + + {{ if $all.Cfg.EnableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }} + + {{ range $location := $server.Locations }} + {{ $path := buildLocation $location }} + {{ $authPath := buildAuthLocation $location }} + + {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }} + # PEM sha: {{ $location.CertificateAuth.AuthSSLCert.PemSHA }} + ssl_client_certificate {{ $location.CertificateAuth.AuthSSLCert.CAFileName }}; + ssl_verify_client on; + ssl_verify_depth {{ $location.CertificateAuth.ValidationDepth }}; + {{ end }} + + {{ if not (empty $location.Redirect.AppRoot)}} + if ($uri = /) { + return 302 {{ $location.Redirect.AppRoot }}; + } + {{ end }} + + {{ if not (empty $authPath) }} + location = {{ $authPath }} { + internal; + set $proxy_upstream_name "internal"; + + {{ if not $location.ExternalAuth.SendBody }} + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + {{ end }} + {{ if not (empty $location.ExternalAuth.Method) }} + proxy_method {{ $location.ExternalAuth.Method }}; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Scheme $pass_access_scheme; + {{ end }} + proxy_pass_request_headers on; + proxy_set_header Host {{ $location.ExternalAuth.Host }}; + proxy_ssl_server_name on; + + client_max_body_size "{{ $location.Proxy.BodySize }}"; + + + set $target {{ $location.ExternalAuth.URL }}; + proxy_pass $target; + } + {{ end }} + + location {{ $path }} { + set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $all.Backends $location }}"; + + {{ if (or $location.Redirect.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect)) }} + # enforce ssl on server side + if ($pass_access_scheme = http) { + return 301 https://$best_http_host$request_uri; + } + {{ end }} + + {{ if isLocationAllowed $location }} + {{ if gt (len $location.Whitelist.CIDR) 0 }} + if ({{ buildDenyVariable (print .Hostname "_" $path) }}) { + return 403; + } + {{ end }} + + port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }}; + + {{ if not (empty $authPath) }} + # this location requires authentication + auth_request {{ $authPath }}; + {{- range $idx, $line := buildAuthResponseHeaders $location }} + {{ $line }} + {{- end }} + {{ end }} + + {{ if not (empty $location.ExternalAuth.SigninURL) }} + error_page 401 = {{ $location.ExternalAuth.SigninURL }}; + {{ end }} + + + {{/* if the location contains a rate limit annotation, create one */}} + {{ $limits := buildRateLimit $location }} + {{ range $limit := $limits }} + {{ $limit }}{{ end }} + + {{ if $location.BasicDigestAuth.Secured }} + {{ if eq $location.BasicDigestAuth.Type "basic" }} + auth_basic "{{ $location.BasicDigestAuth.Realm }}"; + auth_basic_user_file {{ $location.BasicDigestAuth.File }}; + {{ else }} + auth_digest "{{ $location.BasicDigestAuth.Realm }}"; + auth_digest_user_file {{ $location.BasicDigestAuth.File }}; + {{ end }} + proxy_set_header Authorization ""; + {{ end }} + + {{ if $location.EnableCORS }} + {{ template "CORS" }} + {{ end }} + + client_max_body_size "{{ $location.Proxy.BodySize }}"; + + proxy_set_header Host $best_http_host; + + # Pass the extracted client certificate to the backend + {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }} + proxy_set_header ssl-client-cert $ssl_client_cert; + {{ end }} + + # Allow websocket connections + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_set_header X-Real-IP $the_real_ip; + proxy_set_header X-Forwarded-For $the_real_ip; + proxy_set_header X-Forwarded-Host $best_http_host; + proxy_set_header X-Forwarded-Port $pass_port; + proxy_set_header X-Forwarded-Proto $pass_access_scheme; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Scheme $pass_access_scheme; + + # mitigate HTTPoxy Vulnerability + # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ + proxy_set_header Proxy ""; + + # Custom headers to proxied server + {{ range $k, $v := $all.ProxySetHeaders }} + proxy_set_header {{ $k }} "{{ $v }}"; + {{ end }} + + proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s; + proxy_send_timeout {{ $location.Proxy.SendTimeout }}s; + proxy_read_timeout {{ $location.Proxy.ReadTimeout }}s; + + proxy_redirect off; + proxy_buffering off; + proxy_buffer_size "{{ $location.Proxy.BufferSize }}"; + proxy_buffers 4 "{{ $location.Proxy.BufferSize }}"; + + proxy_http_version 1.1; + + proxy_cookie_domain {{ $location.Proxy.CookieDomain }}; + proxy_cookie_path {{ $location.Proxy.CookiePath }}; + + # In case of errors try the next upstream server before returning an error + proxy_next_upstream {{ buildNextUpstream $location.Proxy.NextUpstream }}{{ if $all.Cfg.RetryNonIdempotent }} non_idempotent{{ end }}; + + {{/* rewrite only works if the content is not compressed */}} + {{ if $location.Redirect.AddBaseURL }} + proxy_set_header Accept-Encoding ""; + {{ end }} + + {{/* Add any additional configuration defined */}} + {{ $location.ConfigurationSnippet }} + + {{ buildProxyPass $server.Hostname $all.Backends $location }} + {{ else }} + #{{ $location.Denied }} + return 503; + {{ end }} + } + {{ end }} + + {{ if eq $server.Hostname "_" }} + # health checks in cloud providers require the use of port 80 + location {{ $all.HealthzURI }} { + access_log off; + return 200; + } + + # this is required to avoid error if nginx is being monitored + # with an external software (like sysdig) + location /nginx_status { + allow 127.0.0.1; + {{ if $all.IsIPV6Enabled }}allow ::1;{{ end }} + deny all; + + access_log off; + stub_status on; + } + + {{ end }} + +{{ end }} diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index c7f6444f4..033e2bb81 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -1062,21 +1062,9 @@ func (ic *GenericController) createServers(data []interface{}, continue } - // setup server-aliases based on annotations - aliasMap := map[string]string{} + // setup server-alias based on annotations aliasAnnotation := ic.annotations.Alias(ing) - // Here we parse the annotation string in the following format: - // ingress.kubernetes.io/server-alias: "host_0:alias_0;...;host_n:alias_n" - aliases := strings.Split(aliasAnnotation, ";") - for _, alias := range aliases { - aliasParts := strings.Split(alias, ":") - if len(aliasParts) == 2 { - // aliasMap[host] = alias - aliasMap[aliasParts[0]] = aliasParts[1] - } - } - for _, rule := range ing.Spec.Rules { host := rule.Host if host == "" { @@ -1084,7 +1072,7 @@ func (ic *GenericController) createServers(data []interface{}, } // setup server aliases - servers[host].Alias = aliasMap[host] + servers[host].Alias = aliasAnnotation // only add a certificate if the server does not have one previously configured if len(ing.Spec.TLS) == 0 || servers[host].SSLCertificate != "" { From e12138f4dc720f547bbd585dd19241e63cdb88ee Mon Sep 17 00:00:00 2001 From: Fernando Diaz Date: Thu, 17 Aug 2017 12:05:01 -0500 Subject: [PATCH 3/3] Remove any aliases that conflict with a hostname Removes the alias association if an existing server with the same hostname as the alias exists. This is done to disallow any duplicate server creation when the alias annotation is provided. --- controllers/nginx/pkg/template/template.go | 2 +- controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl | 9 +++++---- core/pkg/ingress/annotations/alias/main.go | 7 +++---- core/pkg/ingress/controller/controller.go | 10 ++++++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/controllers/nginx/pkg/template/template.go b/controllers/nginx/pkg/template/template.go index 605434e03..d4dc1379b 100644 --- a/controllers/nginx/pkg/template/template.go +++ b/controllers/nginx/pkg/template/template.go @@ -490,4 +490,4 @@ func buildNextUpstream(input interface{}) string { } return strings.Join(nextUpstreamCodes, " ") -} +} \ No newline at end of file diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 4b0eada94..13d750819 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -1,4 +1,5 @@ {{ $all := . }} +{{ $servers := .Servers }} {{ $cfg := .Cfg }} {{ $IsIPV6Enabled := .IsIPV6Enabled }} {{ $healthzURI := .HealthzURI }} @@ -269,7 +270,7 @@ http { {{ end }} {{/* build the maps that will be use to validate the Whitelist */}} - {{ range $index, $server := .Servers }} + {{ range $index, $server := $servers }} {{ range $location := $server.Locations }} {{ $path := buildLocation $location }} @@ -288,18 +289,18 @@ http { {{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}} {{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}} - {{ range $zone := (buildRateLimitZones $cfg.LimitConnZoneVariable .Servers) }} + {{ range $zone := (buildRateLimitZones $cfg.LimitConnZoneVariable $servers) }} {{ $zone }} {{ end }} {{ $backlogSize := .BacklogSize }} - {{ range $index, $server := .Servers }} + {{ range $index, $server := $servers }} server { server_name {{ $server.Hostname }}; {{ template "SERVER" serverConfig $all $server }} {{ template "CUSTOM_ERRORS" $all }} } - {{if $server.Alias }} + {{ if $server.Alias }} server { server_name {{ $server.Alias }}; {{ template "SERVER" serverConfig $all $server }} diff --git a/core/pkg/ingress/annotations/alias/main.go b/core/pkg/ingress/annotations/alias/main.go index 1e56d15f3..ddfbab208 100644 --- a/core/pkg/ingress/annotations/alias/main.go +++ b/core/pkg/ingress/annotations/alias/main.go @@ -1,5 +1,5 @@ /* -Copyright 2016 The Kubernetes Authors. +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. @@ -29,14 +29,13 @@ const ( type alias struct { } -// NewParser creates a new CORS annotation parser +// NewParser creates a new Alias annotation parser func NewParser() parser.IngressAnnotation { return alias{} } // Parse parses the annotations contained in the ingress rule -// used to indicate if the location/s contains a fragment of -// configuration to be included inside the paths of the rules +// used to add an alias to the provided hosts func (a alias) Parse(ing *extensions.Ingress) (interface{}, error) { return parser.GetStringAnnotation(annotation, ing) } \ No newline at end of file diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index 033e2bb81..bce28cee6 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -605,6 +605,16 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress upstreams := ic.createUpstreams(ings) servers := ic.createServers(ings, upstreams) + // If a server has a hostname equivalent to a pre-existing alias, then we remove the alias + for _, server := range servers { + for j, alias := range servers { + if server.Hostname == alias.Alias { + glog.Warningf("There is a conflict with hostname '%v' and alias of `%v`.", server.Hostname, alias.Hostname) + servers[j].Alias = "" + } + } + } + for _, ingIf := range ings { ing := ingIf.(*extensions.Ingress)