git.iko.soy/typeable/oauth2-proxy
on ea261ca help sign in
413 lines 14 kB history raw
1package upstream
3import (
4 "crypto"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
10 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
13 . "github.com/onsi/ginkgo"
14 . "github.com/onsi/ginkgo/extensions/table"
15 . "github.com/onsi/gomega"
18var _ = Describe("Proxy Suite", func() {
19 type proxyTableInput struct {
20 target string
21 response testHTTPResponse
22 upstream string
23 upstreams options.UpstreamConfig
24 }
26 Context("multiUpstreamProxy", func() {
27 DescribeTable("Proxy ServeHTTP",
28 func(in *proxyTableInput) {
29 sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"}
31 writer := &pagewriter.WriterFuncs{
32 ProxyErrorFunc: func(rw http.ResponseWriter, _ *http.Request, _ error) {
33 rw.WriteHeader(502)
34 rw.Write([]byte("Proxy Error"))
35 },
36 }
38 ok := http.StatusOK
39 accepted := http.StatusAccepted
41 // Allows for specifying settings and even individual upstreams for specific tests and uses the default upstreams/configs otherwise
42 upstreams := in.upstreams
43 if len(in.upstreams.Upstreams) == 0 {
44 upstreams.Upstreams = []options.Upstream{
45 {
46 ID: "http-backend",
47 Path: "/http/",
48 URI: serverAddr,
49 },
50 {
51 ID: "file-backend",
52 Path: "/files/",
53 URI: fmt.Sprintf("file:///%s", filesDir),
54 },
55 {
56 ID: "static-backend",
57 Path: "/static/",
58 Static: true,
59 StaticCode: &ok,
60 },
61 {
62 ID: "static-backend-no-trailing-slash",
63 Path: "/static",
64 Static: true,
65 StaticCode: &accepted,
66 },
67 {
68 ID: "static-backend-long",
69 Path: "/static/long",
70 Static: true,
71 StaticCode: &accepted,
72 },
73 {
74 ID: "bad-http-backend",
75 Path: "/bad-http/",
76 URI: "http://::1",
77 },
78 {
79 ID: "single-path-backend",
80 Path: "/single-path",
81 Static: true,
82 StaticCode: &ok,
83 },
84 {
85 ID: "backend-with-rewrite-prefix",
86 Path: "^/rewrite-prefix/(.*)",
87 RewriteTarget: "/different/backend/path/$1",
88 URI: serverAddr,
89 },
90 {
91 ID: "double-match-plain",
92 Path: "/double-match/",
93 URI: serverAddr,
94 },
95 {
96 ID: "double-match-rewrite",
97 Path: "^/double-match/(.*)",
98 RewriteTarget: "/double-match/rewrite/$1",
99 URI: serverAddr,
100 },
101 }
102 }
104 upstreamServer, err := NewProxy(upstreams, sigData, writer)
105 Expect(err).ToNot(HaveOccurred())
107 req := middlewareapi.AddRequestScope(
108 httptest.NewRequest("", in.target, nil),
109 &middlewareapi.RequestScope{},
110 )
111 rw := httptest.NewRecorder()
112 // Don't mock the remote Address
113 req.RemoteAddr = ""
115 upstreamServer.ServeHTTP(rw, req)
117 scope := middlewareapi.GetRequestScope(req)
118 Expect(scope.Upstream).To(Equal(in.upstream))
120 Expect(rw.Code).To(Equal(in.response.code))
122 // Delete extra headers that aren't relevant to tests
123 testSanitizeResponseHeader(rw.Header())
124 Expect(rw.Header()).To(Equal(in.response.header))
126 body := rw.Body.Bytes()
127 // If the raw body is set, check that, else check the Request object
128 if in.response.raw != "" {
129 Expect(string(body)).To(Equal(in.response.raw))
130 return
131 }
133 // Compare the reflected request to the upstream
134 if body != nil {
135 request := testHTTPRequest{}
136 Expect(json.Unmarshal(body, &request)).To(Succeed())
137 testSanitizeRequestHeader(request.Header)
138 Expect(request).To(Equal(in.response.request))
139 }
140 },
141 Entry("with a request to the HTTP service", &proxyTableInput{
142 target: "http://example.localhost/http/1234",
143 response: testHTTPResponse{
144 code: 200,
145 header: map[string][]string{
146 contentType: {applicationJSON},
147 },
148 request: testHTTPRequest{
149 Method: "GET",
150 URL: "http://example.localhost/http/1234",
151 Header: map[string][]string{
152 "Gap-Auth": {""},
153 "Gap-Signature": {"sha256 ofB1u6+FhEUbFLc3/uGbJVkl7GaN4egFqVvyO3+2I1w="},
154 },
155 Body: []byte{},
156 Host: "example.localhost",
157 RequestURI: "http://example.localhost/http/1234",
158 },
159 },
160 upstream: "http-backend",
161 }),
162 Entry("with a request to the File backend", &proxyTableInput{
163 target: "http://example.localhost/files/foo",
164 response: testHTTPResponse{
165 code: 200,
166 header: map[string][]string{
167 contentType: {textPlainUTF8},
168 },
169 raw: "foo",
170 },
171 upstream: "file-backend",
172 }),
173 Entry("with a request to the Static backend", &proxyTableInput{
174 target: "http://example.localhost/static/bar",
175 response: testHTTPResponse{
176 code: 200,
177 header: map[string][]string{},
178 raw: "Authenticated",
179 },
180 upstream: "static-backend",
181 }),
182 Entry("with a request to the bad HTTP backend", &proxyTableInput{
183 target: "http://example.localhost/bad-http/bad",
184 response: testHTTPResponse{
185 code: 502,
186 header: map[string][]string{},
187 // This tests the error handler
188 raw: "Proxy Error",
189 },
190 upstream: "bad-http-backend",
191 }),
192 Entry("with a request to the to an unregistered path", &proxyTableInput{
193 target: "http://example.localhost/unregistered",
194 response: testHTTPResponse{
195 code: 404,
196 header: map[string][]string{
197 "X-Content-Type-Options": {"nosniff"},
198 contentType: {textPlainUTF8},
199 },
200 raw: "404 page not found\n",
201 },
202 }),
203 Entry("with a request to the to backend registered to a single path", &proxyTableInput{
204 target: "http://example.localhost/single-path",
205 response: testHTTPResponse{
206 code: 200,
207 header: map[string][]string{},
208 raw: "Authenticated",
209 },
210 upstream: "single-path-backend",
211 }),
212 Entry("with a request to the to a subpath of a backend registered to a single path", &proxyTableInput{
213 target: "http://example.localhost/single-path/unregistered",
214 response: testHTTPResponse{
215 code: 404,
216 header: map[string][]string{
217 "X-Content-Type-Options": {"nosniff"},
218 contentType: {textPlainUTF8},
219 },
220 raw: "404 page not found\n",
221 },
222 }),
223 Entry("with a request to the rewrite prefix server", &proxyTableInput{
224 target: "http://example.localhost/rewrite-prefix/1234",
225 response: testHTTPResponse{
226 code: 200,
227 header: map[string][]string{
228 contentType: {applicationJSON},
229 },
230 request: testHTTPRequest{
231 Method: "GET",
232 URL: "http://example.localhost/different/backend/path/1234",
233 Header: map[string][]string{
234 "Gap-Auth": {""},
235 "Gap-Signature": {"sha256 jeAeM7wHSj2ab/l9YPvtTJ9l/8q1tpY2V/iwXF48bgw="},
236 },
237 Body: []byte{},
238 Host: "example.localhost",
239 RequestURI: "http://example.localhost/different/backend/path/1234",
240 },
241 },
242 upstream: "backend-with-rewrite-prefix",
243 }),
244 Entry("with a request to a subpath of the rewrite prefix server", &proxyTableInput{
245 target: "http://example.localhost/rewrite-prefix/1234/abc",
246 response: testHTTPResponse{
247 code: 200,
248 header: map[string][]string{
249 contentType: {applicationJSON},
250 },
251 request: testHTTPRequest{
252 Method: "GET",
253 URL: "http://example.localhost/different/backend/path/1234/abc",
254 Header: map[string][]string{
255 "Gap-Auth": {""},
256 "Gap-Signature": {"sha256 rAkAc9gp7EndoOppJuvbuPnYuBcqrTkBnQx6iPS8xTA="},
257 },
258 Body: []byte{},
259 Host: "example.localhost",
260 RequestURI: "http://example.localhost/different/backend/path/1234/abc",
261 },
262 },
263 upstream: "backend-with-rewrite-prefix",
264 }),
265 Entry("with a request to a path, missing the trailing slash", &proxyTableInput{
266 target: "http://example.localhost/http",
267 response: testHTTPResponse{
268 code: 301,
269 header: map[string][]string{
270 contentType: {textHTMLUTF8},
271 "Location": {"http://example.localhost/http/"},
272 },
273 raw: "<a href=\"http://example.localhost/http/\">Moved Permanently</a>.\n\n",
274 },
275 }),
276 Entry("with a request to a path, missing the trailing slash, but registered separately", &proxyTableInput{
277 target: "http://example.localhost/static",
278 response: testHTTPResponse{
279 code: 202,
280 header: map[string][]string{},
281 raw: "Authenticated",
282 },
283 upstream: "static-backend-no-trailing-slash",
284 }),
285 Entry("should match longest path first", &proxyTableInput{
286 target: "http://example.localhost/static/long",
287 response: testHTTPResponse{
288 code: 202,
289 header: map[string][]string{},
290 raw: "Authenticated",
291 },
292 upstream: "static-backend-long",
293 }),
294 Entry("should match rewrite path first", &proxyTableInput{
295 target: "http://example.localhost/double-match/foo",
296 response: testHTTPResponse{
297 code: 200,
298 header: map[string][]string{
299 contentType: {applicationJSON},
300 },
301 request: testHTTPRequest{
302 Method: "GET",
303 URL: "http://example.localhost/double-match/rewrite/foo",
304 Header: map[string][]string{
305 "Gap-Auth": {""},
306 "Gap-Signature": {"sha256 eYyUNdsrTmnvFpavpP8AdHGUGzqJ39QEjqn0/3fQPHA="},
307 },
308 Body: []byte{},
309 Host: "example.localhost",
310 RequestURI: "http://example.localhost/double-match/rewrite/foo",
311 },
312 },
313 upstream: "double-match-rewrite",
314 }),
315 Entry("containing an escaped '/' without ProxyRawPath", &proxyTableInput{
316 target: "http://example.localhost/%2F/test1/%2F/test2",
317 response: testHTTPResponse{
318 code: 301,
319 header: map[string][]string{
320 "Location": {
321 "http://example.localhost/test1/test2",
322 },
323 },
324 },
325 upstream: "",
326 }),
327 Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{
328 upstreams: options.UpstreamConfig{ProxyRawPath: true},
329 target: "http://example.localhost/%2F/test1/%2F/test2",
330 response: testHTTPResponse{
331 code: 404,
332 header: map[string][]string{
333 "X-Content-Type-Options": {"nosniff"},
334 contentType: {textPlainUTF8},
335 },
336 raw: "404 page not found\n",
337 },
338 upstream: "",
339 }),
340 )
341 })
343 Context("sortByPathLongest", func() {
344 type sortByPathLongestTableInput struct {
345 input []options.Upstream
346 expectedOutput []options.Upstream
347 }
349 httpPath := options.Upstream{
350 Path: "/http/",
351 }
353 httpSubPath := options.Upstream{
354 Path: "/http/subpath/",
355 }
357 longerPath := options.Upstream{
358 Path: "/longer-than-http",
359 }
361 shortPathWithRewrite := options.Upstream{
362 Path: "^/h/(.*)",
363 RewriteTarget: "/$1",
364 }
366 shortSubPathWithRewrite := options.Upstream{
367 Path: "^/h/bar/(.*)",
368 RewriteTarget: "/$1",
369 }
371 DescribeTable("short sort into the correct order",
372 func(in sortByPathLongestTableInput) {
373 Expect(sortByPathLongest(in.input)).To(Equal(in.expectedOutput))
374 },
375 Entry("with a mix of paths registered", sortByPathLongestTableInput{
376 input: []options.Upstream{httpPath, httpSubPath, shortSubPathWithRewrite, longerPath, shortPathWithRewrite},
377 expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite, longerPath, httpSubPath, httpPath},
378 }),
379 Entry("when a subpath is registered (in order)", sortByPathLongestTableInput{
380 input: []options.Upstream{httpSubPath, httpPath},
381 expectedOutput: []options.Upstream{httpSubPath, httpPath},
382 }),
383 Entry("when a subpath is registered (out of order)", sortByPathLongestTableInput{
384 input: []options.Upstream{httpPath, httpSubPath},
385 expectedOutput: []options.Upstream{httpSubPath, httpPath},
386 }),
387 Entry("when longer paths are registered (in order)", sortByPathLongestTableInput{
388 input: []options.Upstream{longerPath, httpPath},
389 expectedOutput: []options.Upstream{longerPath, httpPath},
390 }),
391 Entry("when longer paths are registered (out of order)", sortByPathLongestTableInput{
392 input: []options.Upstream{httpPath, longerPath},
393 expectedOutput: []options.Upstream{longerPath, httpPath},
394 }),
395 Entry("when a rewrite target is registered (in order)", sortByPathLongestTableInput{
396 input: []options.Upstream{shortPathWithRewrite, longerPath},
397 expectedOutput: []options.Upstream{shortPathWithRewrite, longerPath},
398 }),
399 Entry("when a rewrite target is registered (out of order)", sortByPathLongestTableInput{
400 input: []options.Upstream{longerPath, shortPathWithRewrite},
401 expectedOutput: []options.Upstream{shortPathWithRewrite, longerPath},
402 }),
403 Entry("with multiple rewrite targets registered (in order)", sortByPathLongestTableInput{
404 input: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
405 expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
406 }),
407 Entry("with multiple rewrite targets registered (out of order)", sortByPathLongestTableInput{
408 input: []options.Upstream{shortPathWithRewrite, shortSubPathWithRewrite},
409 expectedOutput: []options.Upstream{shortSubPathWithRewrite, shortPathWithRewrite},
410 }),
411 )
412 })
413})