From 62fea9aa014404a7ebd05f5ff3892422dd96dd8f Mon Sep 17 00:00:00 2001 From: Fernando Diaz Date: Tue, 15 Aug 2017 01:23:19 -0500 Subject: [PATCH] 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 != "" {