1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-03-25 22:00:56 +02:00

Ensure upstreams are sorted by longest first

This commit is contained in:
Joel Speed 2021-03-07 14:51:38 +00:00
parent 8a06779d41
commit 075cb9c3a0
No known key found for this signature in database
GPG Key ID: 6E80578D6751DEFB
2 changed files with 386 additions and 235 deletions

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
"sort"
"strings" "strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -26,7 +27,7 @@ func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, write
serveMux: mux.NewRouter(), serveMux: mux.NewRouter(),
} }
for _, upstream := range upstreams { for _, upstream := range sortByPathLongest(upstreams) {
if upstream.Static { if upstream.Static {
if err := m.registerStaticResponseHandler(upstream, writer); err != nil { if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err) return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
@ -145,3 +146,33 @@ func registerTrailingSlashHandler(serveMux *mux.Router) {
http.Redirect(rw, req, req.URL.String()+"/", http.StatusMovedPermanently) http.Redirect(rw, req, req.URL.String()+"/", http.StatusMovedPermanently)
})) }))
} }
// sortByPathLongest ensures that the upstreams are sorted by longest path.
// If rewrites are involved, a rewrite takes precedence over a non-rewrite.
// When two upstreams define rewrites, whichever has the longest path will take
// precedence (note this is the input to the rewrite logic).
// This does not account for when a rewrite would actually make the path shorter.
// This should maintain the sorting behaviour of the standard go serve mux.
func sortByPathLongest(in options.Upstreams) options.Upstreams {
sort.Slice(in, func(i, j int) bool {
iRW := in[i].RewriteTarget
jRW := in[j].RewriteTarget
switch {
case iRW != "" && jRW != "":
// If both have a rewrite target, whichever has the longest pattern
// should go first
return len(in[i].Path) > len(in[j].Path)
case iRW != "" && jRW == "":
// Only one has rewrite, it goes first
return true
case iRW == "" && jRW != "":
// Only one has rewrite, it goes first
return false
default:
// Default to longest Path wins
return len(in[i].Path) > len(in[j].Path)
}
})
return in
}

View File

@ -18,6 +18,7 @@ import (
var _ = Describe("Proxy Suite", func() { var _ = Describe("Proxy Suite", func() {
var upstreamServer http.Handler var upstreamServer http.Handler
Context("multiUpstreamProxy", func() {
BeforeEach(func() { BeforeEach(func() {
sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"} sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
@ -54,6 +55,12 @@ var _ = Describe("Proxy Suite", func() {
Static: true, Static: true,
StaticCode: &accepted, StaticCode: &accepted,
}, },
{
ID: "static-backend-long",
Path: "/static/long",
Static: true,
StaticCode: &accepted,
},
{ {
ID: "bad-http-backend", ID: "bad-http-backend",
Path: "/bad-http/", Path: "/bad-http/",
@ -71,6 +78,17 @@ var _ = Describe("Proxy Suite", func() {
RewriteTarget: "/different/backend/path/$1", RewriteTarget: "/different/backend/path/$1",
URI: serverAddr, URI: serverAddr,
}, },
{
ID: "double-match-plain",
Path: "/double-match/",
URI: serverAddr,
},
{
ID: "double-match-rewrite",
Path: "^/double-match/(.*)",
RewriteTarget: "/double-match/rewrite/$1",
URI: serverAddr,
},
} }
var err error var err error
@ -262,5 +280,107 @@ var _ = Describe("Proxy Suite", func() {
}, },
upstream: "static-backend-no-trailing-slash", upstream: "static-backend-no-trailing-slash",
}), }),
Entry("should match longest path first", &proxyTableInput{
target: "http://example.localhost/static/long",
response: testHTTPResponse{
code: 202,
header: map[string][]string{},
raw: "Authenticated",
},
upstream: "static-backend-long",
}),
Entry("should match rewrite path first", &proxyTableInput{
target: "http://example.localhost/double-match/foo",
response: testHTTPResponse{
code: 200,
header: map[string][]string{
contentType: {applicationJSON},
},
request: testHTTPRequest{
Method: "GET",
URL: "http://example.localhost/double-match/rewrite/foo",
Header: map[string][]string{
"Gap-Auth": {""},
"Gap-Signature": {"sha256 eYyUNdsrTmnvFpavpP8AdHGUGzqJ39QEjqn0/3fQPHA="},
},
Body: []byte{},
Host: "example.localhost",
RequestURI: "http://example.localhost/double-match/rewrite/foo",
},
},
upstream: "double-match-rewrite",
}),
) )
}) })
Context("sortByPathLongest", func() {
type sortByPathLongestTableInput struct {
input options.Upstreams
expectedOutput options.Upstreams
}
var httpPath = options.Upstream{
Path: "/http/",
}
var httpSubPath = options.Upstream{
Path: "/http/subpath/",
}
var longerPath = options.Upstream{
Path: "/longer-than-http",
}
var shortPathWithRewrite = options.Upstream{
Path: "^/h/(.*)",
RewriteTarget: "/$1",
}
var shortSubPathWithRewrite = options.Upstream{
Path: "^/h/bar/(.*)",
RewriteTarget: "/$1",
}
DescribeTable("short sort into the correct order",
func(in sortByPathLongestTableInput) {
Expect(sortByPathLongest(in.input)).To(Equal(in.expectedOutput))
},
Entry("with a mix of paths registered", sortByPathLongestTableInput{
input: options.Upstreams{httpPath, httpSubPath, shortSubPathWithRewrite, longerPath, shortPathWithRewrite},
expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite, longerPath, httpSubPath, httpPath},
}),
Entry("when a subpath is registered (in order)", sortByPathLongestTableInput{
input: options.Upstreams{httpSubPath, httpPath},
expectedOutput: options.Upstreams{httpSubPath, httpPath},
}),
Entry("when a subpath is registered (out of order)", sortByPathLongestTableInput{
input: options.Upstreams{httpPath, httpSubPath},
expectedOutput: options.Upstreams{httpSubPath, httpPath},
}),
Entry("when longer paths are registered (in order)", sortByPathLongestTableInput{
input: options.Upstreams{longerPath, httpPath},
expectedOutput: options.Upstreams{longerPath, httpPath},
}),
Entry("when longer paths are registered (out of order)", sortByPathLongestTableInput{
input: options.Upstreams{httpPath, longerPath},
expectedOutput: options.Upstreams{longerPath, httpPath},
}),
Entry("when a rewrite target is registered (in order)", sortByPathLongestTableInput{
input: options.Upstreams{shortPathWithRewrite, longerPath},
expectedOutput: options.Upstreams{shortPathWithRewrite, longerPath},
}),
Entry("when a rewrite target is registered (out of order)", sortByPathLongestTableInput{
input: options.Upstreams{longerPath, shortPathWithRewrite},
expectedOutput: options.Upstreams{shortPathWithRewrite, longerPath},
}),
Entry("with multiple rewrite targets registered (in order)", sortByPathLongestTableInput{
input: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite},
expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite},
}),
Entry("with multiple rewrite targets registered (out of order)", sortByPathLongestTableInput{
input: options.Upstreams{shortPathWithRewrite, shortSubPathWithRewrite},
expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite},
}),
)
})
})