Skip to content

Commit

Permalink
Add support for exact paths when specified as locations (#197)
Browse files Browse the repository at this point in the history
Goes some way to resolving #192.

Right now, if an ingress specifies a path of e.g. /my/test/path, this will be rendered in the location block as location /my/test/path/. If a browser then hits /my/test/path it will be redirected to /my/test/path/ with a 301 permanent redirect.

If /my/test/path is not a directory, but is instead an absolute path to a resource, then redirecting to /my/test/path/ simply doesn't make sense. This PR introduces the concept of "exact paths" by allowing ingresses to specify if all of their paths are absolute paths to resources. If so, we render the path as location = <raw path value>.
  • Loading branch information
lw346 authored and alangibson01 committed Mar 22, 2019
1 parent ee39342 commit b56c00f
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v1.12.4
* Add support for exact paths when specified as locations
https://github.com/sky-uk/feed/pull/197

# v1.12.3
* New flag to set the worker shutdown timeout

Expand Down
6 changes: 6 additions & 0 deletions cmd/feed-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const (
defaultIngressAllow = "0.0.0.0/0"
defaultIngressHealthPort = 8081
defaultIngressStripPath = true
defaultIngressExactPath = false
defaultHealthPort = 12082
defaultNginxBinary = "/usr/sbin/nginx"
defaultNginxWorkingDir = "/nginx"
Expand Down Expand Up @@ -159,6 +160,11 @@ func init() {
"limitations. URL encoded characters will not work correctly in some cases, and backend services will "+
"need to take care to properly construct URLs, such as by using the 'X-Original-URI' header."+
"Can be overridden with the sky.uk/strip-path annotation per ingress")
flag.BoolVar(&controllerConfig.DefaultExactPath, "ingress-exact-path", defaultIngressExactPath,
"Whether to consider the ingress path to be an exact match rather than as a prefix. For example, "+
"if enabled 'myhost/myapp/health' would match 'myhost/myapp/health' but not 'myhost/myapp/health/x'."+
" If disabled, it would match both (and redirect requests from 'myhost/myapp/health' to "+
" '/myhost/myapp/health/'. Can be overridden with the sky.uk/exact-path annotation per ingress")
flag.IntVar(&healthPort, "health-port", defaultHealthPort,
"Port for checking the health of the ingress controller on /health. Also provides /debug/pprof.")

Expand Down
15 changes: 15 additions & 0 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
// Deprecated: retained to maintain backwards compatibility.
frontendElbSchemeAnnotation = "sky.uk/frontend-elb-scheme"
stripPathAnnotation = "sky.uk/strip-path"
exactPathAnnotation = "sky.uk/exact-path"

// Old annotation - still supported to maintain backwards compatibility.
legacyBackendKeepAliveSeconds = "sky.uk/backend-keepalive-seconds"
Expand Down Expand Up @@ -53,6 +54,7 @@ type controller struct {
updaters []Updater
defaultAllow []string
defaultStripPath bool
defaultExactPath bool
defaultBackendTimeout int
defaultBackendMaxConnections int
defaultProxyBufferSize int
Expand All @@ -71,6 +73,7 @@ type Config struct {
Updaters []Updater
DefaultAllow string
DefaultStripPath bool
DefaultExactPath bool
DefaultBackendTimeoutSeconds int
DefaultBackendMaxConnections int
DefaultProxyBufferSize int
Expand All @@ -84,6 +87,7 @@ func New(conf Config) Controller {
updaters: conf.Updaters,
defaultAllow: strings.Split(conf.DefaultAllow, ","),
defaultStripPath: conf.DefaultStripPath,
defaultExactPath: conf.DefaultExactPath,
defaultBackendTimeout: conf.DefaultBackendTimeoutSeconds,
defaultBackendMaxConnections: conf.DefaultBackendMaxConnections,
defaultProxyBufferSize: conf.DefaultProxyBufferSize,
Expand Down Expand Up @@ -182,6 +186,7 @@ func (c *controller) updateIngresses() error {
ServicePort: int32(path.Backend.ServicePort.IntValue()),
Allow: c.defaultAllow,
StripPaths: c.defaultStripPath,
ExactPath: c.defaultExactPath,
BackendTimeoutSeconds: c.defaultBackendTimeout,
BackendMaxConnections: c.defaultBackendMaxConnections,
ProxyBufferSize: c.defaultProxyBufferSize,
Expand Down Expand Up @@ -216,6 +221,16 @@ func (c *controller) updateIngresses() error {
}
}

if exactPath, ok := ingress.Annotations[exactPathAnnotation]; ok {
if exactPath == "true" {
entry.ExactPath = true
} else if exactPath == "false" {
entry.ExactPath = false
} else {
log.Warnf("Ingress %s has an invalid exact path annotation: %s. Using default", ingress.Name, exactPath)
}
}

if backendKeepAlive, ok := ingress.Annotations[legacyBackendKeepAliveSeconds]; ok {
tmp, _ := strconv.Atoi(backendKeepAlive)
entry.BackendTimeoutSeconds = tmp
Expand Down
50 changes: 50 additions & 0 deletions controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,54 @@ func TestUpdaterIsUpdatedOnK8sUpdates(t *testing.T) {
}},
defaultConfig(),
},
{
"ingress with exact path set to true",
createIngressesFixture(ingressHost, ingressSvcName, ingressSvcPort,
map[string]string{
ingressAllowAnnotation: "",
exactPathAnnotation: "true",
backendTimeoutSeconds: "10",
frontendElbSchemeAnnotation: "internal",
}),
createDefaultServices(),
[]IngressEntry{{
Namespace: ingressNamespace,
Name: ingressName,
Host: ingressHost,
Path: ingressPath,
ServiceAddress: serviceIP,
ServicePort: ingressSvcPort,
LbScheme: "internal",
Allow: []string{},
ExactPath: true,
BackendTimeoutSeconds: backendTimeout,
}},
defaultConfig(),
},
{
"ingress with exact path set to false",
createIngressesFixture(ingressHost, ingressSvcName, ingressSvcPort,
map[string]string{
ingressAllowAnnotation: "",
exactPathAnnotation: "false",
backendTimeoutSeconds: "10",
frontendElbSchemeAnnotation: "internal",
}),
createDefaultServices(),
[]IngressEntry{{
Namespace: ingressNamespace,
Name: ingressName,
Host: ingressHost,
Path: ingressPath,
ServiceAddress: serviceIP,
ServicePort: ingressSvcPort,
LbScheme: "internal",
Allow: []string{},
ExactPath: false,
BackendTimeoutSeconds: backendTimeout,
}},
defaultConfig(),
},
{
"ingress with overridden backend timeout",
createIngressesFixture(ingressHost, ingressSvcName, ingressSvcPort,
Expand Down Expand Up @@ -780,6 +828,8 @@ func createIngressesFixture(host string, serviceName string, servicePort int, in
annotations[ingressAllowAnnotation] = annotationVal
case stripPathAnnotation:
annotations[stripPathAnnotation] = annotationVal
case exactPathAnnotation:
annotations[exactPathAnnotation] = annotationVal
case frontendElbSchemeAnnotation:
annotations[frontendElbSchemeAnnotation] = annotationVal
case frontendSchemeAnnotation:
Expand Down
2 changes: 2 additions & 0 deletions controller/ingress_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type IngressEntry struct {
LbScheme string
// StripPaths before forwarding to the backend
StripPaths bool
// ExactPath indicates that the Path should be treated as an exact match rather than a prefix
ExactPath bool
// BackendTimeoutSeconds backend timeout
BackendTimeoutSeconds int
// BackendMaxConnections maximum backend connections
Expand Down
10 changes: 8 additions & 2 deletions nginx/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type location struct {
UpstreamID string
Allow []string
StripPath bool
ExactPath bool
BackendTimeoutSeconds int
ProxyBufferSize int
ProxyBufferBlocks int
Expand Down Expand Up @@ -461,6 +462,7 @@ func createServerEntries(entries controller.IngressEntries) []*server {
UpstreamID: upstreamID(ingressEntry),
Allow: ingressEntry.Allow,
StripPath: ingressEntry.StripPaths,
ExactPath: ingressEntry.ExactPath,
BackendTimeoutSeconds: ingressEntry.BackendTimeoutSeconds,
ProxyBufferSize: ingressEntry.ProxyBufferSize,
ProxyBufferBlocks: ingressEntry.ProxyBufferBlocks,
Expand Down Expand Up @@ -497,7 +499,7 @@ func uniqueIngressEntries(entries controller.IngressEntries) []controller.Ingres

uniqueIngress := make(map[ingressKey]controller.IngressEntry)
for _, ingressEntry := range entries {
ingressEntry.Path = createNginxPath(ingressEntry.Path)
ingressEntry.Path = createNginxPath(ingressEntry.Path, ingressEntry.ExactPath)
key := ingressKey{ingressEntry.Host, ingressEntry.Path}
existingIngressEntry, exists := uniqueIngress[key]
if !exists {
Expand All @@ -515,7 +517,11 @@ func uniqueIngressEntries(entries controller.IngressEntries) []controller.Ingres
return uniqueIngressEntries
}

func createNginxPath(rawPath string) string {
func createNginxPath(rawPath string, exactPath bool) string {
if exactPath {
return rawPath
}

nginxPath := strings.TrimSuffix(strings.TrimPrefix(rawPath, "/"), "/")
if len(nginxPath) == 0 {
nginxPath = "/"
Expand Down
2 changes: 1 addition & 1 deletion nginx/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ http {

{{- range $location := $entry.Locations }}

location {{ if $location.Path }}{{ $location.Path }}{{ end }} {
location {{ if $location.Path }}{{ if $location.ExactPath }}= {{ end }}{{ $location.Path }}{{ end }} {
{{- if $location.StripPath }}
# Strip location path when proxying.
# Beware this can cause issues with url encoded characters.
Expand Down
21 changes: 21 additions & 0 deletions nginx/nginx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ func TestNginxIngressEntries(t *testing.T) {
ServicePort: 8080,
Allow: []string{"10.82.0.0/16"},
StripPaths: true,
ExactPath: false,
BackendTimeoutSeconds: 1,
},
{
Expand All @@ -493,6 +494,7 @@ func TestNginxIngressEntries(t *testing.T) {
ServicePort: 6060,
Allow: []string{"10.86.0.0/16"},
StripPaths: false,
ExactPath: false,
BackendTimeoutSeconds: 10,
BackendMaxConnections: 1024,
},
Expand Down Expand Up @@ -793,6 +795,25 @@ func TestNginxIngressEntries(t *testing.T) {
" location /prefix-without-anyslash/ {\n",
},
},
{
"Check exact paths include equals",
defaultConf,
[]controller.IngressEntry{
{
Host: "chris-0.com",
Namespace: "core",
Name: "chris-ingress",
Path: "/a/test/path",
ServiceAddress: "service",
ServicePort: 9090,
ExactPath: true,
},
},
nil,
[]string{
" location = /a/test/path {\n",
},
},
{
"Check multiple allows work",
defaultConf,
Expand Down

0 comments on commit b56c00f

Please sign in to comment.