1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-01-22 05:19:26 +02:00
oauth2-proxy/pkg/upstream/proxy_test.go
Damien Degois 70571d96e1
Add support for unix socket as upstream (#1866)
* Add support for unix socket as upstream

* Add CHANGELOG.md entry

* Add Unix socket documentation

* Don't export unixRoundTripper, switch from string prefix to Scheme match

* Add basic unix server mock

* Add some tests and comments
2023-10-26 10:57:00 +01:00

440 lines
14 KiB
Go

package upstream
import (
"crypto"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
var _ = Describe("Proxy Suite", func() {
type proxyTableInput struct {
target string
response testHTTPResponse
upstream string
upstreams options.UpstreamConfig
}
Context("multiUpstreamProxy", func() {
DescribeTable("Proxy ServeHTTP",
func(in *proxyTableInput) {
sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
writer := &pagewriter.WriterFuncs{
ProxyErrorFunc: func(rw http.ResponseWriter, _ *http.Request, _ error) {
rw.WriteHeader(502)
rw.Write([]byte("Proxy Error"))
},
}
ok := http.StatusOK
accepted := http.StatusAccepted
// Allows for specifying settings and even individual upstreams for specific tests and uses the default upstreams/configs otherwise
upstreams := in.upstreams
if len(in.upstreams.Upstreams) == 0 {
upstreams.Upstreams = []options.Upstream{
{
ID: "http-backend",
Path: "/http/",
URI: serverAddr,
},
{
ID: "file-backend",
Path: "/files/",
URI: fmt.Sprintf("file:///%s", filesDir),
},
{
ID: "static-backend",
Path: "/static/",
Static: true,
StaticCode: &ok,
},
{
ID: "static-backend-no-trailing-slash",
Path: "/static",
Static: true,
StaticCode: &accepted,
},
{
ID: "static-backend-long",
Path: "/static/long",
Static: true,
StaticCode: &accepted,
},
{
ID: "bad-http-backend",
Path: "/bad-http/",
URI: "http://::1",
},
{
ID: "single-path-backend",
Path: "/single-path",
Static: true,
StaticCode: &ok,
},
{
ID: "backend-with-rewrite-prefix",
Path: "^/rewrite-prefix/(.*)",
RewriteTarget: "/different/backend/path/$1",
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,
},
{
ID: "unix-upstream",
Path: "/unix/",
URI: unixServerAddr,
},
}
}
upstreamServer, err := NewProxy(upstreams, sigData, writer)
Expect(err).ToNot(HaveOccurred())
req := middlewareapi.AddRequestScope(
httptest.NewRequest("", in.target, nil),
&middlewareapi.RequestScope{},
)
rw := httptest.NewRecorder()
// Don't mock the remote Address
req.RemoteAddr = ""
upstreamServer.ServeHTTP(rw, req)
scope := middlewareapi.GetRequestScope(req)
Expect(scope.Upstream).To(Equal(in.upstream))
Expect(rw.Code).To(Equal(in.response.code))
// Delete extra headers that aren't relevant to tests
testSanitizeResponseHeader(rw.Header())
Expect(rw.Header()).To(Equal(in.response.header))
body := rw.Body.Bytes()
// If the raw body is set, check that, else check the Request object
if in.response.raw != "" {
Expect(string(body)).To(Equal(in.response.raw))
return
}
// Compare the reflected request to the upstream
if body != nil {
request := testHTTPRequest{}
Expect(json.Unmarshal(body, &request)).To(Succeed())
testSanitizeRequestHeader(request.Header)
Expect(request).To(Equal(in.response.request))
}
},
Entry("with a request to the HTTP service", &proxyTableInput{
target: "http://example.localhost/http/1234",
response: testHTTPResponse{
code: 200,
header: map[string][]string{
contentType: {applicationJSON},
},
request: testHTTPRequest{
Method: "GET",
URL: "http://example.localhost/http/1234",
Header: map[string][]string{
"Gap-Auth": {""},
"Gap-Signature": {"sha256 ofB1u6+FhEUbFLc3/uGbJVkl7GaN4egFqVvyO3+2I1w="},
},
Body: []byte{},
Host: "example.localhost",
RequestURI: "http://example.localhost/http/1234",
},
},
upstream: "http-backend",
}),
Entry("with a request to the File backend", &proxyTableInput{
target: "http://example.localhost/files/foo",
response: testHTTPResponse{
code: 200,
header: map[string][]string{
contentType: {textPlainUTF8},
},
raw: "foo",
},
upstream: "file-backend",
}),
Entry("with a request to the Static backend", &proxyTableInput{
target: "http://example.localhost/static/bar",
response: testHTTPResponse{
code: 200,
header: map[string][]string{},
raw: "Authenticated",
},
upstream: "static-backend",
}),
Entry("with a request to the bad HTTP backend", &proxyTableInput{
target: "http://example.localhost/bad-http/bad",
response: testHTTPResponse{
code: 502,
header: map[string][]string{},
// This tests the error handler
raw: "Proxy Error",
},
upstream: "bad-http-backend",
}),
Entry("with a request to the to an unregistered path", &proxyTableInput{
target: "http://example.localhost/unregistered",
response: testHTTPResponse{
code: 404,
header: map[string][]string{
"X-Content-Type-Options": {"nosniff"},
contentType: {textPlainUTF8},
},
raw: "404 page not found\n",
},
}),
Entry("with a request to the to backend registered to a single path", &proxyTableInput{
target: "http://example.localhost/single-path",
response: testHTTPResponse{
code: 200,
header: map[string][]string{},
raw: "Authenticated",
},
upstream: "single-path-backend",
}),
Entry("with a request to the to a subpath of a backend registered to a single path", &proxyTableInput{
target: "http://example.localhost/single-path/unregistered",
response: testHTTPResponse{
code: 404,
header: map[string][]string{
"X-Content-Type-Options": {"nosniff"},
contentType: {textPlainUTF8},
},
raw: "404 page not found\n",
},
}),
Entry("with a request to the rewrite prefix server", &proxyTableInput{
target: "http://example.localhost/rewrite-prefix/1234",
response: testHTTPResponse{
code: 200,
header: map[string][]string{
contentType: {applicationJSON},
},
request: testHTTPRequest{
Method: "GET",
URL: "http://example.localhost/different/backend/path/1234",
Header: map[string][]string{
"Gap-Auth": {""},
"Gap-Signature": {"sha256 jeAeM7wHSj2ab/l9YPvtTJ9l/8q1tpY2V/iwXF48bgw="},
},
Body: []byte{},
Host: "example.localhost",
RequestURI: "http://example.localhost/different/backend/path/1234",
},
},
upstream: "backend-with-rewrite-prefix",
}),
Entry("with a request to a subpath of the rewrite prefix server", &proxyTableInput{
target: "http://example.localhost/rewrite-prefix/1234/abc",
response: testHTTPResponse{
code: 200,
header: map[string][]string{
contentType: {applicationJSON},
},
request: testHTTPRequest{
Method: "GET",
URL: "http://example.localhost/different/backend/path/1234/abc",
Header: map[string][]string{
"Gap-Auth": {""},
"Gap-Signature": {"sha256 rAkAc9gp7EndoOppJuvbuPnYuBcqrTkBnQx6iPS8xTA="},
},
Body: []byte{},
Host: "example.localhost",
RequestURI: "http://example.localhost/different/backend/path/1234/abc",
},
},
upstream: "backend-with-rewrite-prefix",
}),
Entry("with a request to a path, missing the trailing slash", &proxyTableInput{
target: "http://example.localhost/http",
response: testHTTPResponse{
code: 301,
header: map[string][]string{
contentType: {textHTMLUTF8},
"Location": {"http://example.localhost/http/"},
},
raw: "<a href=\"http://example.localhost/http/\">Moved Permanently</a>.\n\n",
},
}),
Entry("with a request to a path, missing the trailing slash, but registered separately", &proxyTableInput{
target: "http://example.localhost/static",
response: testHTTPResponse{
code: 202,
header: map[string][]string{},
raw: "Authenticated",
},
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",
}),
Entry("containing an escaped '/' without ProxyRawPath", &proxyTableInput{
target: "http://example.localhost/%2F/test1/%2F/test2",
response: testHTTPResponse{
code: 301,
header: map[string][]string{
"Location": {
"http://example.localhost/test1/test2",
},
},
},
upstream: "",
}),
Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{
upstreams: options.UpstreamConfig{ProxyRawPath: true},
target: "http://example.localhost/%2F/test1/%2F/test2",
response: testHTTPResponse{
code: 404,
header: map[string][]string{
"X-Content-Type-Options": {"nosniff"},
contentType: {textPlainUTF8},
},
raw: "404 page not found\n",
},
upstream: "",
}),
Entry("with a request to the UNIX socket backend", &proxyTableInput{
target: "http://example.localhost/unix/file",
response: testHTTPResponse{
code: 200,
header: map[string][]string{
contentType: {applicationJSON},
},
request: testHTTPRequest{
Method: "GET",
URL: "http://example.localhost/unix/file",
Header: map[string][]string{
"Gap-Auth": {""},
"Gap-Signature": {"sha256 4ux8esLj2fw9sTWZwgFhb00bGbw0Fnhed5Fm9jz5Blw="},
},
Body: []byte{},
Host: "example.localhost",
RequestURI: "http://example.localhost/unix/file",
},
},
upstream: "unix-upstream",
}),
)
})
Context("sortByPathLongest", func() {
type sortByPathLongestTableInput struct {
input []options.Upstream
expectedOutput []options.Upstream
}
httpPath := options.Upstream{
Path: "/http/",
}
httpSubPath := options.Upstream{
Path: "/http/subpath/",
}
longerPath := options.Upstream{
Path: "/longer-than-http",
}
shortPathWithRewrite := options.Upstream{
Path: "^/h/(.*)",
RewriteTarget: "/$1",
}
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.Upstream{httpPath, httpSubPath, shortSubPathWithRewrite, longerPath, shortPathWithRewrite},
expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite, longerPath, httpSubPath, httpPath},
}),
Entry("when a subpath is registered (in order)", sortByPathLongestTableInput{
input: []options.Upstream{httpSubPath, httpPath},
expectedOutput: []options.Upstream{httpSubPath, httpPath},
}),
Entry("when a subpath is registered (out of order)", sortByPathLongestTableInput{
input: []options.Upstream{httpPath, httpSubPath},
expectedOutput: []options.Upstream{httpSubPath, httpPath},
}),
Entry("when longer paths are registered (in order)", sortByPathLongestTableInput{
input: []options.Upstream{longerPath, httpPath},
expectedOutput: []options.Upstream{longerPath, httpPath},
}),
Entry("when longer paths are registered (out of order)", sortByPathLongestTableInput{
input: []options.Upstream{httpPath, longerPath},
expectedOutput: []options.Upstream{longerPath, httpPath},
}),
Entry("when a rewrite target is registered (in order)", sortByPathLongestTableInput{
input: []options.Upstream{shortPathWithRewrite, longerPath},
expectedOutput: []options.Upstream{shortPathWithRewrite, longerPath},
}),
Entry("when a rewrite target is registered (out of order)", sortByPathLongestTableInput{
input: []options.Upstream{longerPath, shortPathWithRewrite},
expectedOutput: []options.Upstream{shortPathWithRewrite, longerPath},
}),
Entry("with multiple rewrite targets registered (in order)", sortByPathLongestTableInput{
input: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
}),
Entry("with multiple rewrite targets registered (out of order)", sortByPathLongestTableInput{
input: []options.Upstream{shortPathWithRewrite, shortSubPathWithRewrite},
expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
}),
)
})
})