From 2ba255afce3be8a13923b6af6d9aa26a8031740f Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Sat, 19 Aug 2017 16:20:58 -0300 Subject: [PATCH] Add support for temporal and permanent redirects --- .../rootfs/etc/nginx/template/nginx.tmpl | 420 +++++++++--------- .../ingress/annotations/redirect/redirect.go | 140 ++++++ core/pkg/ingress/controller/annotations.go | 4 +- core/pkg/ingress/types.go | 8 +- core/pkg/ingress/types_equals.go | 3 + 5 files changed, 370 insertions(+), 205 deletions(-) create mode 100644 core/pkg/ingress/annotations/redirect/redirect.go diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index e8d8a8f27..a1520e369 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 }} @@ -266,7 +267,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 }} @@ -285,215 +286,24 @@ 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 }}; - 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 }}; - 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 "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 @@ -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'; } {{ 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 }} \ No newline at end of file diff --git a/core/pkg/ingress/annotations/redirect/redirect.go b/core/pkg/ingress/annotations/redirect/redirect.go new file mode 100644 index 000000000..16daa72b1 --- /dev/null +++ b/core/pkg/ingress/annotations/redirect/redirect.go @@ -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 +} diff --git a/core/pkg/ingress/controller/annotations.go b/core/pkg/ingress/controller/annotations.go index ee7a08757..4e791d601 100644 --- a/core/pkg/ingress/controller/annotations.go +++ b/core/pkg/ingress/controller/annotations.go @@ -29,6 +29,7 @@ import ( "k8s.io/ingress/core/pkg/ingress/annotations/portinredirect" "k8s.io/ingress/core/pkg/ingress/annotations/proxy" "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/secureupstream" "k8s.io/ingress/core/pkg/ingress/annotations/serviceupstream" @@ -63,7 +64,8 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { "UsePortInRedirects": portinredirect.NewParser(cfg), "Proxy": proxy.NewParser(cfg), "RateLimit": ratelimit.NewParser(cfg), - "Redirect": rewrite.NewParser(cfg), + "Redirect": redirect.NewParser(), + "Rewrite": rewrite.NewParser(cfg), "SecureUpstream": secureupstream.NewParser(cfg), "ServiceUpstream": serviceupstream.NewParser(), "SessionAffinity": sessionaffinity.NewParser(), diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 5d17fe0d6..bf0007678 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -32,6 +32,7 @@ import ( "k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist" "k8s.io/ingress/core/pkg/ingress/annotations/proxy" "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/defaults" "k8s.io/ingress/core/pkg/ingress/resolver" @@ -274,9 +275,12 @@ type Location struct { // The Redirect annotation precedes RateLimit // +optional 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 - Redirect rewrite.Redirect `json:"redirect,omitempty"` + Rewrite rewrite.Redirect `json:"rewrite,omitempty"` // Whitelist indicates only connections from certain client // addresses or networks are allowed. // +optional diff --git a/core/pkg/ingress/types_equals.go b/core/pkg/ingress/types_equals.go index 81cd4409a..5244e6f86 100644 --- a/core/pkg/ingress/types_equals.go +++ b/core/pkg/ingress/types_equals.go @@ -361,6 +361,9 @@ func (l1 *Location) Equal(l2 *Location) bool { if !(&l1.Redirect).Equal(&l2.Redirect) { return false } + if !(&l1.Rewrite).Equal(&l2.Rewrite) { + return false + } if !(&l1.Whitelist).Equal(&l2.Whitelist) { return false }