Merge pull request #1805 from maxlaverse/add_x_forwarded_prefix

Add X-Forwarded-Prefix on rewrites
This commit is contained in:
Manuel Alejandro de Brito Fontes 2017-12-09 12:16:54 -06:00 committed by GitHub
commit d14d220ea4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 151 additions and 25 deletions

View file

@ -51,6 +51,7 @@ Key:
| `add-base-url` | Add `<base>` tag to HTML. | | nginx | `add-base-url` | Add `<base>` tag to HTML. | | nginx
| `base-url-scheme` | Specify the scheme of the `<base>` tags. | | nginx | `base-url-scheme` | Specify the scheme of the `<base>` tags. | | nginx
| `preserve-host` | Whether to pass the client request host (`true`) or the origin hostname (`false`) in the HTTP Host field. | | trafficserver | `preserve-host` | Whether to pass the client request host (`true`) or the origin hostname (`false`) in the HTTP Host field. | | trafficserver
| `x-forwarded-prefix` | Add the non-standard `X-Forwarded-Prefix` header to the request with the value of the matched location. | | nginx
## CORS Related ## CORS Related
| Name | Meaning | Default | Controller | Name | Meaning | Default | Controller

View file

@ -47,6 +47,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/upstreamhashby" "k8s.io/ingress-nginx/internal/ingress/annotations/upstreamhashby"
"k8s.io/ingress-nginx/internal/ingress/annotations/upstreamvhost" "k8s.io/ingress-nginx/internal/ingress/annotations/upstreamvhost"
"k8s.io/ingress-nginx/internal/ingress/annotations/vtsfilterkey" "k8s.io/ingress-nginx/internal/ingress/annotations/vtsfilterkey"
"k8s.io/ingress-nginx/internal/ingress/annotations/xforwardedprefix"
"k8s.io/ingress-nginx/internal/ingress/errors" "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
@ -81,6 +82,7 @@ type Ingress struct {
UpstreamVhost string UpstreamVhost string
VtsFilterKey string VtsFilterKey string
Whitelist ipwhitelist.SourceRange Whitelist ipwhitelist.SourceRange
XForwardedPrefix bool
} }
// Extractor defines the annotation parsers to be used in the extraction of annotations // Extractor defines the annotation parsers to be used in the extraction of annotations
@ -115,6 +117,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"UpstreamVhost": upstreamvhost.NewParser(cfg), "UpstreamVhost": upstreamvhost.NewParser(cfg),
"VtsFilterKey": vtsfilterkey.NewParser(cfg), "VtsFilterKey": vtsfilterkey.NewParser(cfg),
"Whitelist": ipwhitelist.NewParser(cfg), "Whitelist": ipwhitelist.NewParser(cfg),
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
}, },
} }
} }

View file

@ -0,0 +1,39 @@
/*
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 xforwardedprefix
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type xforwardedprefix struct {
r resolver.Resolver
}
// NewParser creates a new xforwardedprefix annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return xforwardedprefix{r}
}
// Parse parses the annotations contained in the ingress rule
// used to add an x-forwarded-prefix header to the request
func (cbbs xforwardedprefix) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation("x-forwarded-prefix", ing)
}

View file

@ -0,0 +1,62 @@
/*
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 xforwardedprefix
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func TestParse(t *testing.T) {
annotation := parser.GetAnnotationWithPrefix("x-forwarded-prefix")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected bool
}{
{map[string]string{annotation: "true"}, true},
{map[string]string{annotation: "1"}, true},
{map[string]string{annotation: ""}, false},
{map[string]string{}, false},
{nil, false},
}
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)
}
}
}

View file

@ -473,6 +473,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
loc.VtsFilterKey = anns.VtsFilterKey loc.VtsFilterKey = anns.VtsFilterKey
loc.Whitelist = anns.Whitelist loc.Whitelist = anns.Whitelist
loc.Denied = anns.Denied loc.Denied = anns.Denied
loc.XForwardedPrefix = anns.XForwardedPrefix
if loc.Redirect.FromToWWW { if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true server.RedirectFromToWWW = true
@ -503,6 +504,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
VtsFilterKey: anns.VtsFilterKey, VtsFilterKey: anns.VtsFilterKey,
Whitelist: anns.Whitelist, Whitelist: anns.Whitelist,
Denied: anns.Denied, Denied: anns.Denied,
XForwardedPrefix: anns.XForwardedPrefix,
} }
if loc.Redirect.FromToWWW { if loc.Redirect.FromToWWW {

View file

@ -324,20 +324,25 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
} }
} }
xForwardedPrefix := ""
if location.XForwardedPrefix {
xForwardedPrefix = fmt.Sprintf(`proxy_set_header X-Forwarded-Prefix "%s";
`, path)
}
if location.Rewrite.Target == slash { if location.Rewrite.Target == slash {
// special case redirect to / // special case redirect to /
// ie /something to / // ie /something to /
return fmt.Sprintf(` return fmt.Sprintf(`
rewrite %s(.*) /$1 break; rewrite %s(.*) /$1 break;
rewrite %s / break; rewrite %s / break;
proxy_pass %s://%s; %vproxy_pass %s://%s;
%v`, path, location.Path, proto, upstreamName, abu) %v`, path, location.Path, xForwardedPrefix, proto, upstreamName, abu)
} }
return fmt.Sprintf(` return fmt.Sprintf(`
rewrite %s(.*) %s/$1 break; rewrite %s(.*) %s/$1 break;
proxy_pass %s://%s; %vproxy_pass %s://%s;
%v`, path, location.Rewrite.Target, proto, upstreamName, abu) %v`, path, location.Rewrite.Target, xForwardedPrefix, proto, upstreamName, abu)
} }
// default proxy_pass // default proxy_pass

View file

@ -43,57 +43,63 @@ var (
AddBaseURL bool AddBaseURL bool
BaseURLScheme string BaseURLScheme string
Sticky bool Sticky bool
XForwardedPrefix bool
}{ }{
"invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false, "", false}, "invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false, "", false, false},
"redirect / to /jenkins": {"/", "/jenkins", "~* /", "redirect / to /jenkins": {"/", "/jenkins", "~* /",
` `
rewrite /(.*) /jenkins/$1 break; rewrite /(.*) /jenkins/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
`, false, "", false}, `, false, "", false, false},
"redirect /something to /": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, ` "redirect /something to /": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
rewrite /something/(.*) /$1 break; rewrite /something/(.*) /$1 break;
rewrite /something / break; rewrite /something / break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
`, false, "", false}, `, false, "", false, false},
"redirect /end-with-slash/ to /not-root": {"/end-with-slash/", "/not-root", "~* ^/end-with-slash/(?<baseuri>.*)", ` "redirect /end-with-slash/ to /not-root": {"/end-with-slash/", "/not-root", "~* ^/end-with-slash/(?<baseuri>.*)", `
rewrite /end-with-slash/(.*) /not-root/$1 break; rewrite /end-with-slash/(.*) /not-root/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
`, false, "", false}, `, false, "", false, false},
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, ` "redirect /something-complex to /not-root": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
rewrite /something-complex/(.*) /not-root/$1 break; rewrite /something-complex/(.*) /not-root/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
`, false, "", false}, `, false, "", false, false},
"redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", ` "redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", `
rewrite /(.*) /jenkins/$1 break; rewrite /(.*) /jenkins/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/$baseuri">' ro; subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/$baseuri">' ro;
`, true, "", false}, `, true, "", false, false},
"redirect /something to / and rewrite": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, ` "redirect /something to / and rewrite": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
rewrite /something/(.*) /$1 break; rewrite /something/(.*) /$1 break;
rewrite /something / break; rewrite /something / break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/something/$baseuri">' ro; subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/something/$baseuri">' ro;
`, true, "", false}, `, true, "", false, false},
"redirect /end-with-slash/ to /not-root and rewrite": {"/end-with-slash/", "/not-root", `~* ^/end-with-slash/(?<baseuri>.*)`, ` "redirect /end-with-slash/ to /not-root and rewrite": {"/end-with-slash/", "/not-root", `~* ^/end-with-slash/(?<baseuri>.*)`, `
rewrite /end-with-slash/(.*) /not-root/$1 break; rewrite /end-with-slash/(.*) /not-root/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/end-with-slash/$baseuri">' ro; subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/end-with-slash/$baseuri">' ro;
`, true, "", false}, `, true, "", false, false},
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, ` "redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
rewrite /something-complex/(.*) /not-root/$1 break; rewrite /something-complex/(.*) /not-root/$1 break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/something-complex/$baseuri">' ro; subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="$scheme://$http_host/something-complex/$baseuri">' ro;
`, true, "", false}, `, true, "", false, false},
"redirect /something to / and rewrite with specific scheme": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, ` "redirect /something to / and rewrite with specific scheme": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
rewrite /something/(.*) /$1 break; rewrite /something/(.*) /$1 break;
rewrite /something / break; rewrite /something / break;
proxy_pass http://upstream-name; proxy_pass http://upstream-name;
subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="http://$http_host/something/$baseuri">' ro; subs_filter '(<(?:H|h)(?:E|e)(?:A|a)(?:D|d)(?:[^">]|"[^"]*")*>)' '$1<base href="http://$http_host/something/$baseuri">' ro;
`, true, "http", false}, `, true, "http", false, false},
"redirect / to /something with sticky enabled": {"/", "/something", `~* /`, ` "redirect / to /something with sticky enabled": {"/", "/something", `~* /`, `
rewrite /(.*) /something/$1 break; rewrite /(.*) /something/$1 break;
proxy_pass http://sticky-upstream-name; proxy_pass http://sticky-upstream-name;
`, false, "http", true}, `, false, "http", true, false},
"add the X-Forwarded-Prefix header": {"/there", "/something", `~* ^/there\/?(?<baseuri>.*)`, `
rewrite /there/(.*) /something/$1 break;
proxy_set_header X-Forwarded-Prefix "/there/";
proxy_pass http://sticky-upstream-name;
`, false, "http", true, true},
} }
) )
@ -139,6 +145,7 @@ func TestBuildProxyPass(t *testing.T) {
Path: tc.Path, Path: tc.Path,
Rewrite: rewrite.Config{Target: tc.Target, AddBaseURL: tc.AddBaseURL, BaseURLScheme: tc.BaseURLScheme}, Rewrite: rewrite.Config{Target: tc.Target, AddBaseURL: tc.AddBaseURL, BaseURLScheme: tc.BaseURLScheme},
Backend: defaultBackend, Backend: defaultBackend,
XForwardedPrefix: tc.XForwardedPrefix,
} }
backends := []*ingress.Backend{} backends := []*ingress.Backend{}

View file

@ -259,6 +259,10 @@ type Location struct {
// DefaultBackend allows the use of a custom default backend for this location. // DefaultBackend allows the use of a custom default backend for this location.
// +optional // +optional
DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"` DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"`
// XForwardedPrefix allows to add a header X-Forwarded-Prefix to the request with the
// original location.
// +optional
XForwardedPrefix bool `json:"xForwardedPrefix,omitempty"`
} }
// SSLPassthroughBackend describes a SSL upstream server configured // SSLPassthroughBackend describes a SSL upstream server configured

View file

@ -367,6 +367,9 @@ func (l1 *Location) Equal(l2 *Location) bool {
if l1.UpstreamVhost != l2.UpstreamVhost { if l1.UpstreamVhost != l2.UpstreamVhost {
return false return false
} }
if l1.XForwardedPrefix != l2.XForwardedPrefix {
return false
}
return true return true
} }