Add support for temporal and permanent redirects
This commit is contained in:
parent
ad723a7028
commit
2ba255afce
5 changed files with 370 additions and 205 deletions
|
@ -1,4 +1,5 @@
|
||||||
{{ $all := . }}
|
{{ $all := . }}
|
||||||
|
{{ $servers := .Servers }}
|
||||||
{{ $cfg := .Cfg }}
|
{{ $cfg := .Cfg }}
|
||||||
{{ $IsIPV6Enabled := .IsIPV6Enabled }}
|
{{ $IsIPV6Enabled := .IsIPV6Enabled }}
|
||||||
{{ $healthzURI := .HealthzURI }}
|
{{ $healthzURI := .HealthzURI }}
|
||||||
|
@ -266,7 +267,7 @@ http {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{/* build the maps that will be use to validate the Whitelist */}}
|
{{/* build the maps that will be use to validate the Whitelist */}}
|
||||||
{{ range $index, $server := .Servers }}
|
{{ range $index, $server := $servers }}
|
||||||
{{ range $location := $server.Locations }}
|
{{ range $location := $server.Locations }}
|
||||||
{{ $path := buildLocation $location }}
|
{{ $path := buildLocation $location }}
|
||||||
|
|
||||||
|
@ -285,215 +286,24 @@ http {
|
||||||
|
|
||||||
{{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}}
|
{{/* 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 */}}
|
{{/* 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 }}
|
{{ $zone }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ $backlogSize := .BacklogSize }}
|
{{ $backlogSize := .BacklogSize }}
|
||||||
{{ range $index, $server := .Servers }}
|
{{ range $index, $server := $servers }}
|
||||||
server {
|
server {
|
||||||
server_name {{ $server.Hostname }};
|
server_name {{ $server.Hostname }};
|
||||||
listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{end}};
|
{{ template "SERVER" serverConfig $all $server }}
|
||||||
{{ 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 }};
|
|
||||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
|
||||||
add_header Set-Cookie $auth_cookie;
|
|
||||||
{{- range $idx, $line := buildAuthResponseHeaders $location }}
|
|
||||||
{{ $line }}
|
|
||||||
{{- end }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if not (empty $location.ExternalAuth.SigninURL) }}
|
|
||||||
error_page 401 = {{ $location.ExternalAuth.SigninURL }}?rd=$request_uri;
|
|
||||||
{{ 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 }}
|
|
||||||
|
|
||||||
{{ template "CUSTOM_ERRORS" $all }}
|
{{ template "CUSTOM_ERRORS" $all }}
|
||||||
}
|
}
|
||||||
|
{{ if $server.Alias }}
|
||||||
|
server {
|
||||||
|
server_name {{ $server.Alias }};
|
||||||
|
{{ template "SERVER" serverConfig $all $server }}
|
||||||
|
{{ template "CUSTOM_ERRORS" $all }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
# default server, used for NGINX healthcheck and access to nginx stats
|
# default server, used for NGINX healthcheck and access to nginx stats
|
||||||
|
@ -664,3 +474,209 @@ 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';
|
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 }}
|
{{ 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 }};
|
||||||
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
|
add_header Set-Cookie $auth_cookie;
|
||||||
|
{{- range $idx, $line := buildAuthResponseHeaders $location }}
|
||||||
|
{{ $line }}
|
||||||
|
{{- end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if not (empty $location.ExternalAuth.SigninURL) }}
|
||||||
|
error_page 401 = {{ $location.ExternalAuth.SigninURL }}?rd=$request_uri;
|
||||||
|
{{ 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 }}
|
140
core/pkg/ingress/annotations/redirect/redirect.go
Normal file
140
core/pkg/ingress/annotations/redirect/redirect.go
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
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 redirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
permanent = "ingress.kubernetes.io/permanent-redirect"
|
||||||
|
temporal = "ingress.kubernetes.io/temporal-redirect"
|
||||||
|
toWWW = "ingress.kubernetes.io/non-www-to-www-redirect"
|
||||||
|
toNonWWW = "ingress.kubernetes.io/www-to-non-www-redirect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redirect returns the redirect configuration for an Ingress rule
|
||||||
|
type Redirect struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
ToWWW bool `json:"to-www"`
|
||||||
|
ToNonWWW bool `json:"to-non-www"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type redirect struct{}
|
||||||
|
|
||||||
|
// NewParser creates a new redirect annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return redirect{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress
|
||||||
|
// rule used to create a redirect in the paths defined in the rule.
|
||||||
|
// If the Ingress containes both annotations the execution order is
|
||||||
|
// temporal and then permanent
|
||||||
|
func (a redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
towww, _ := parser.GetBoolAnnotation(toWWW, ing)
|
||||||
|
tononwww, _ := parser.GetBoolAnnotation(toNonWWW, ing)
|
||||||
|
|
||||||
|
tr, err := parser.GetStringAnnotation(temporal, ing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tr != "" {
|
||||||
|
if err := isValidURL(tr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Redirect{
|
||||||
|
URL: tr,
|
||||||
|
Code: http.StatusFound,
|
||||||
|
ToWWW: towww,
|
||||||
|
ToNonWWW: tononwww,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, err := parser.GetStringAnnotation(permanent, ing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr != "" {
|
||||||
|
if err := isValidURL(pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Redirect{
|
||||||
|
URL: pr,
|
||||||
|
Code: http.StatusMovedPermanently,
|
||||||
|
ToWWW: towww,
|
||||||
|
ToNonWWW: tononwww,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if towww || tononwww {
|
||||||
|
return &Redirect{
|
||||||
|
ToWWW: towww,
|
||||||
|
ToNonWWW: tononwww,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("ingress rule without redirect annotations")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two Redirect types
|
||||||
|
func (r1 *Redirect) Equal(r2 *Redirect) bool {
|
||||||
|
if r1 == r2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if r1 == nil || r2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.URL != r2.URL {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.Code != r2.Code {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.ToWWW != r2.ToWWW {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.ToNonWWW != r2.ToNonWWW {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidURL(s string) error {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(u.Scheme, "http") {
|
||||||
|
return errors.Errorf("only http and https are valid protocols (%v)", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/portinredirect"
|
"k8s.io/ingress/core/pkg/ingress/annotations/portinredirect"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/redirect"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/secureupstream"
|
"k8s.io/ingress/core/pkg/ingress/annotations/secureupstream"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/serviceupstream"
|
"k8s.io/ingress/core/pkg/ingress/annotations/serviceupstream"
|
||||||
|
@ -63,7 +64,8 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
||||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||||
"Proxy": proxy.NewParser(cfg),
|
"Proxy": proxy.NewParser(cfg),
|
||||||
"RateLimit": ratelimit.NewParser(cfg),
|
"RateLimit": ratelimit.NewParser(cfg),
|
||||||
"Redirect": rewrite.NewParser(cfg),
|
"Redirect": redirect.NewParser(),
|
||||||
|
"Rewrite": rewrite.NewParser(cfg),
|
||||||
"SecureUpstream": secureupstream.NewParser(cfg),
|
"SecureUpstream": secureupstream.NewParser(cfg),
|
||||||
"ServiceUpstream": serviceupstream.NewParser(),
|
"ServiceUpstream": serviceupstream.NewParser(),
|
||||||
"SessionAffinity": sessionaffinity.NewParser(),
|
"SessionAffinity": sessionaffinity.NewParser(),
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/redirect"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
|
@ -274,9 +275,12 @@ type Location struct {
|
||||||
// The Redirect annotation precedes RateLimit
|
// The Redirect annotation precedes RateLimit
|
||||||
// +optional
|
// +optional
|
||||||
RateLimit ratelimit.RateLimit `json:"rateLimit,omitempty"`
|
RateLimit ratelimit.RateLimit `json:"rateLimit,omitempty"`
|
||||||
// Redirect describes the redirection this location.
|
// Redirect describes a temporal o permanent redirection this location.
|
||||||
|
// +optional // +optional
|
||||||
|
Redirect redirect.Redirect `json:"redirect,omitempty"`
|
||||||
|
// Rewrite describes the redirection this location.
|
||||||
// +optional
|
// +optional
|
||||||
Redirect rewrite.Redirect `json:"redirect,omitempty"`
|
Rewrite rewrite.Redirect `json:"rewrite,omitempty"`
|
||||||
// Whitelist indicates only connections from certain client
|
// Whitelist indicates only connections from certain client
|
||||||
// addresses or networks are allowed.
|
// addresses or networks are allowed.
|
||||||
// +optional
|
// +optional
|
||||||
|
|
|
@ -361,6 +361,9 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
||||||
if !(&l1.Redirect).Equal(&l2.Redirect) {
|
if !(&l1.Redirect).Equal(&l2.Redirect) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !(&l1.Rewrite).Equal(&l2.Rewrite) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !(&l1.Whitelist).Equal(&l2.Whitelist) {
|
if !(&l1.Whitelist).Equal(&l2.Whitelist) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue