Config/Annotations: Add relative-redirects
. (#12161)
This commit is contained in:
parent
0207d1878a
commit
698960e9b7
8 changed files with 182 additions and 0 deletions
|
@ -103,6 +103,7 @@
|
||||||
| Redirect | from-to-www-redirect | Low | location |
|
| Redirect | from-to-www-redirect | Low | location |
|
||||||
| Redirect | permanent-redirect | Medium | location |
|
| Redirect | permanent-redirect | Medium | location |
|
||||||
| Redirect | permanent-redirect-code | Low | location |
|
| Redirect | permanent-redirect-code | Low | location |
|
||||||
|
| Redirect | relative-redirects | Low | location |
|
||||||
| Redirect | temporal-redirect | Medium | location |
|
| Redirect | temporal-redirect | Medium | location |
|
||||||
| Redirect | temporal-redirect-code | Low | location |
|
| Redirect | temporal-redirect-code | Low | location |
|
||||||
| Rewrite | app-root | Medium | location |
|
| Rewrite | app-root | Medium | location |
|
||||||
|
|
|
@ -223,6 +223,7 @@ The following table shows a configuration option's name, type, and the default v
|
||||||
| [debug-connections](#debug-connections) | []string | "127.0.0.1,1.1.1.1/24" | |
|
| [debug-connections](#debug-connections) | []string | "127.0.0.1,1.1.1.1/24" | |
|
||||||
| [strict-validate-path-type](#strict-validate-path-type) | bool | "true" | |
|
| [strict-validate-path-type](#strict-validate-path-type) | bool | "true" | |
|
||||||
| [grpc-buffer-size-kb](#grpc-buffer-size-kb) | int | 0 | |
|
| [grpc-buffer-size-kb](#grpc-buffer-size-kb) | int | 0 | |
|
||||||
|
| [relative-redirects](#relative-redirects) | bool | false | |
|
||||||
|
|
||||||
## add-headers
|
## add-headers
|
||||||
|
|
||||||
|
@ -1382,3 +1383,14 @@ Sets the configuration for the GRPC Buffer Size parameter. If not set it will us
|
||||||
|
|
||||||
_References:_
|
_References:_
|
||||||
[https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size](https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size)
|
[https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size](https://nginx.org/en/docs/http/ngx_http_grpc_module.html#grpc_buffer_size)
|
||||||
|
|
||||||
|
## relative-redirects
|
||||||
|
|
||||||
|
Use relative redirects instead of absolute redirects. Absolute redirects are the default in nginx. RFC7231 allows relative redirects since 2014.
|
||||||
|
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/relative-redirects`.
|
||||||
|
|
||||||
|
_**default:**_ "false"
|
||||||
|
|
||||||
|
_References:_
|
||||||
|
- [https://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect](https://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect)
|
||||||
|
- [https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2)
|
||||||
|
|
|
@ -38,6 +38,7 @@ type Config struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
FromToWWW bool `json:"fromToWWW"`
|
FromToWWW bool `json:"fromToWWW"`
|
||||||
|
Relative bool `json:"relative"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -46,6 +47,7 @@ const (
|
||||||
temporalRedirectAnnotationCode = "temporal-redirect-code"
|
temporalRedirectAnnotationCode = "temporal-redirect-code"
|
||||||
permanentRedirectAnnotation = "permanent-redirect"
|
permanentRedirectAnnotation = "permanent-redirect"
|
||||||
permanentRedirectAnnotationCode = "permanent-redirect-code"
|
permanentRedirectAnnotationCode = "permanent-redirect-code"
|
||||||
|
relativeRedirectsAnnotation = "relative-redirects"
|
||||||
)
|
)
|
||||||
|
|
||||||
var redirectAnnotations = parser.Annotation{
|
var redirectAnnotations = parser.Annotation{
|
||||||
|
@ -83,6 +85,12 @@ var redirectAnnotations = parser.Annotation{
|
||||||
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
|
||||||
Documentation: `This annotation allows you to modify the status code used for permanent redirects.`,
|
Documentation: `This annotation allows you to modify the status code used for permanent redirects.`,
|
||||||
},
|
},
|
||||||
|
relativeRedirectsAnnotation: {
|
||||||
|
Validator: parser.ValidateBool,
|
||||||
|
Scope: parser.AnnotationScopeLocation,
|
||||||
|
Risk: parser.AnnotationRiskLow,
|
||||||
|
Documentation: `If enabled, redirects issued by nginx will be relative. See https://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +117,11 @@ func (r redirect) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rr, err := parser.GetBoolAnnotation(relativeRedirectsAnnotation, ing, r.annotationConfig.Annotations)
|
||||||
|
if err != nil && !errors.IsMissingAnnotations(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
tr, err := parser.GetStringAnnotation(temporalRedirectAnnotation, ing, r.annotationConfig.Annotations)
|
tr, err := parser.GetStringAnnotation(temporalRedirectAnnotation, ing, r.annotationConfig.Annotations)
|
||||||
if err != nil && !errors.IsMissingAnnotations(err) {
|
if err != nil && !errors.IsMissingAnnotations(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -132,6 +145,7 @@ func (r redirect) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
URL: tr,
|
URL: tr,
|
||||||
Code: trc,
|
Code: trc,
|
||||||
FromToWWW: r3w,
|
FromToWWW: r3w,
|
||||||
|
Relative: rr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +168,13 @@ func (r redirect) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
URL: pr,
|
URL: pr,
|
||||||
Code: prc,
|
Code: prc,
|
||||||
FromToWWW: r3w,
|
FromToWWW: r3w,
|
||||||
|
Relative: rr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if rr {
|
||||||
|
return &Config{
|
||||||
|
Relative: rr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +198,9 @@ func (r1 *Config) Equal(r2 *Config) bool {
|
||||||
if r1.FromToWWW != r2.FromToWWW {
|
if r1.FromToWWW != r2.FromToWWW {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if r1.Relative != r2.Relative {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,3 +193,22 @@ func TestIsValidURL(t *testing.T) {
|
||||||
t.Errorf("expected nil but got %v", err)
|
t.Errorf("expected nil but got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAnnotations(t *testing.T) {
|
||||||
|
ing := new(networking.Ingress)
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[parser.GetAnnotationWithPrefix(relativeRedirectsAnnotation)] = "true"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
_, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test ingress using the annotation without a TLS section
|
||||||
|
_, err = NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error parsing ingress with relative-redirects")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -549,6 +549,10 @@ type Configuration struct {
|
||||||
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors
|
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors
|
||||||
DisableProxyInterceptErrors bool `json:"disable-proxy-intercept-errors,omitempty"`
|
DisableProxyInterceptErrors bool `json:"disable-proxy-intercept-errors,omitempty"`
|
||||||
|
|
||||||
|
// Disable absolute redirects and enables relative redirects.
|
||||||
|
// https://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect
|
||||||
|
RelativeRedirects bool `json:"relative-redirects"`
|
||||||
|
|
||||||
// Sets the ipv4 addresses on which the server will accept requests.
|
// Sets the ipv4 addresses on which the server will accept requests.
|
||||||
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
|
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
|
||||||
|
|
||||||
|
@ -834,6 +838,7 @@ func NewDefault() Configuration {
|
||||||
VariablesHashMaxSize: 2048,
|
VariablesHashMaxSize: 2048,
|
||||||
UseHTTP2: true,
|
UseHTTP2: true,
|
||||||
DisableProxyInterceptErrors: false,
|
DisableProxyInterceptErrors: false,
|
||||||
|
RelativeRedirects: false,
|
||||||
ProxyStreamTimeout: "600s",
|
ProxyStreamTimeout: "600s",
|
||||||
ProxyStreamNextUpstream: true,
|
ProxyStreamNextUpstream: true,
|
||||||
ProxyStreamNextUpstreamTimeout: "600s",
|
ProxyStreamNextUpstreamTimeout: "600s",
|
||||||
|
@ -857,6 +862,7 @@ func NewDefault() Configuration {
|
||||||
SSLRedirect: true,
|
SSLRedirect: true,
|
||||||
CustomHTTPErrors: []int{},
|
CustomHTTPErrors: []int{},
|
||||||
DisableProxyInterceptErrors: false,
|
DisableProxyInterceptErrors: false,
|
||||||
|
RelativeRedirects: false,
|
||||||
DenylistSourceRange: []string{},
|
DenylistSourceRange: []string{},
|
||||||
WhitelistSourceRange: []string{},
|
WhitelistSourceRange: []string{},
|
||||||
SkipAccessLogURLs: []string{},
|
SkipAccessLogURLs: []string{},
|
||||||
|
|
|
@ -125,6 +125,11 @@ type Backend struct {
|
||||||
// Default: false
|
// Default: false
|
||||||
UsePortInRedirects bool `json:"use-port-in-redirects"`
|
UsePortInRedirects bool `json:"use-port-in-redirects"`
|
||||||
|
|
||||||
|
// Enables or disables relative redirects. By default nginx uses absolute redirects.
|
||||||
|
// http://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect
|
||||||
|
// Default: false
|
||||||
|
RelativeRedirects bool `json:"relative-redirects"`
|
||||||
|
|
||||||
// Enable stickiness by client-server mapping based on a NGINX variable, text or a combination of both.
|
// Enable stickiness by client-server mapping based on a NGINX variable, text or a combination of both.
|
||||||
// A consistent hashing method will be used which ensures only a few keys would be remapped to different
|
// A consistent hashing method will be used which ensures only a few keys would be remapped to different
|
||||||
// servers on upstream group changes
|
// servers on upstream group changes
|
||||||
|
|
|
@ -459,6 +459,10 @@ http {
|
||||||
proxy_intercept_errors on;
|
proxy_intercept_errors on;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if $cfg.RelativeRedirects }}
|
||||||
|
absolute_redirect off;
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{ range $errCode := $cfg.CustomHTTPErrors }}
|
{{ range $errCode := $cfg.CustomHTTPErrors }}
|
||||||
error_page {{ $errCode }} = @custom_upstream-default-backend_{{ $errCode }};{{ end }}
|
error_page {{ $errCode }} = @custom_upstream-default-backend_{{ $errCode }};{{ end }}
|
||||||
|
|
||||||
|
@ -1343,6 +1347,10 @@ stream {
|
||||||
satisfy {{ $location.Satisfy }};
|
satisfy {{ $location.Satisfy }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if $location.Redirect.Relative }}
|
||||||
|
absolute_redirect off;
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{/* if a location-specific error override is set, add the proxy_intercept here */}}
|
{{/* if a location-specific error override is set, add the proxy_intercept here */}}
|
||||||
{{ if and $location.CustomHTTPErrors (not $location.DisableProxyInterceptErrors) }}
|
{{ if and $location.CustomHTTPErrors (not $location.DisableProxyInterceptErrors) }}
|
||||||
# Custom error pages per ingress
|
# Custom error pages per ingress
|
||||||
|
|
107
test/e2e/annotations/relativeredirects.go
Normal file
107
test/e2e/annotations/relativeredirects.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 annotations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
relativeRedirectsHostname = "rr.foo.com"
|
||||||
|
relativeRedirectsRedirectPath = "/something"
|
||||||
|
relativeRedirectsRelativeRedirectURL = "/new-location"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = framework.DescribeAnnotation("relative-redirects", func() {
|
||||||
|
f := framework.NewDefaultFramework("relative-redirects")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewHttpbunDeployment()
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("configures Nginx correctly", func() {
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/relative-redirects": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(relativeRedirectsHostname, "/", relativeRedirectsHostname, f.Namespace, framework.HTTPBunService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
var serverConfig string
|
||||||
|
f.WaitForNginxServer(relativeRedirectsHostname, func(srvCfg string) bool {
|
||||||
|
serverConfig = srvCfg
|
||||||
|
return strings.Contains(serverConfig, fmt.Sprintf("server_name %s", relativeRedirectsHostname))
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.By("turning off absolute_redirect directive")
|
||||||
|
assert.Contains(ginkgo.GinkgoT(), serverConfig, "absolute_redirect off;")
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should respond with absolute URL in Location", func() {
|
||||||
|
absoluteRedirectURL := fmt.Sprintf("http://%s%s", relativeRedirectsHostname, relativeRedirectsRelativeRedirectURL)
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/permanent-redirect": relativeRedirectsRelativeRedirectURL,
|
||||||
|
"nginx.ingress.kubernetes.io/relative-redirects": "false",
|
||||||
|
}
|
||||||
|
|
||||||
|
ginkgo.By("setup ingress")
|
||||||
|
ing := framework.NewSingleIngress(relativeRedirectsHostname, relativeRedirectsRedirectPath, relativeRedirectsHostname, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(relativeRedirectsHostname, func(srvCfg string) bool {
|
||||||
|
return strings.Contains(srvCfg, fmt.Sprintf("server_name %s", relativeRedirectsHostname))
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.By("sending request to redirected URL path")
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET(relativeRedirectsRedirectPath).
|
||||||
|
WithHeader("Host", relativeRedirectsHostname).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusMovedPermanently).
|
||||||
|
Header("Location").Equal(absoluteRedirectURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should respond with relative URL in Location", func() {
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/permanent-redirect": relativeRedirectsRelativeRedirectURL,
|
||||||
|
"nginx.ingress.kubernetes.io/relative-redirects": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
ginkgo.By("setup ingress")
|
||||||
|
ing := framework.NewSingleIngress(relativeRedirectsHostname, relativeRedirectsRedirectPath, relativeRedirectsHostname, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(relativeRedirectsHostname, func(srvCfg string) bool {
|
||||||
|
return strings.Contains(srvCfg, fmt.Sprintf("server_name %s", relativeRedirectsHostname))
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.By("sending request to redirected URL path")
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET(relativeRedirectsRedirectPath).
|
||||||
|
WithHeader("Host", relativeRedirectsHostname).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusMovedPermanently).
|
||||||
|
Header("Location").Equal(relativeRedirectsRelativeRedirectURL)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue