Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for exact paths when specified as locations #197

Merged
merged 6 commits into from
Mar 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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