16 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
17 . "github.com/onsi/ginkgo"
18 . "github.com/onsi/ginkgo/extensions/table"
19 . "github.com/onsi/gomega"
20 "golang.org/x/net/websocket"
23var _ = Describe("HTTP Upstream Suite", func() {
25 const flushInterval5s = 5 * time.Second
26 const flushInterval1s = 1 * time.Second
28 type httpUpstreamTableInput struct {
34 signatureData *options.SignatureData
35 existingHeaders map[string]string
36 expectedResponse testHTTPResponse
37 errorHandler ProxyErrorHandler
40 DescribeTable("HTTP Upstream ServeHTTP",
41 func(in *httpUpstreamTableInput) {
42 buf := bytes.NewBuffer(in.body)
43 req := httptest.NewRequest(in.method, in.target, buf)
44 // Don't mock the remote Address
47 for key, value := range in.existingHeaders {
48 req.Header.Add(key, value)
51 rw := httptest.NewRecorder()
53 flush := 1 * time.Second
54 upstream := options.Upstream{
57 ProxyWebSockets: false,
58 InsecureSkipTLSVerify: false,
59 FlushInterval: &flush,
62 Expect(in.serverAddr).ToNot(BeNil())
63 u, err := url.Parse(*in.serverAddr)
64 Expect(err).ToNot(HaveOccurred())
66 handler := newHTTPUpstreamProxy(upstream, u, in.signatureData, in.errorHandler)
67 handler.ServeHTTP(rw, req)
69 Expect(rw.Code).To(Equal(in.expectedResponse.code))
71 // Delete extra headers that aren't relevant to tests
72 testSanitizeResponseHeader(rw.Header())
73 Expect(rw.Header()).To(Equal(in.expectedResponse.header))
75 body := rw.Body.Bytes()
76 if in.expectedResponse.raw != "" || rw.Code != http.StatusOK {
77 Expect(string(body)).To(Equal(in.expectedResponse.raw))
81 // Compare the reflected request to the upstream
82 request := testHTTPRequest{}
83 Expect(json.Unmarshal(body, &request)).To(Succeed())
84 testSanitizeRequestHeader(request.Header)
85 Expect(request).To(Equal(in.expectedResponse.request))
87 Entry("request a path on the server", &httpUpstreamTableInput{
89 serverAddr: &serverAddr,
90 target: "http://example.localhost/foo",
94 expectedResponse: testHTTPResponse{
96 header: map[string][]string{
97 gapUpstream: {"default"},
98 contentType: {applicationJSON},
100 request: testHTTPRequest{
102 URL: "http://example.localhost/foo",
103 Header: map[string][]string{},
105 Host: "example.localhost",
106 RequestURI: "http://example.localhost/foo",
110 Entry("request a path with encoded slashes", &httpUpstreamTableInput{
111 id: "encodedSlashes",
112 serverAddr: &serverAddr,
113 target: "http://example.localhost/foo%2fbar/?baz=1",
117 expectedResponse: testHTTPResponse{
119 header: map[string][]string{
120 gapUpstream: {"encodedSlashes"},
121 contentType: {applicationJSON},
123 request: testHTTPRequest{
125 URL: "http://example.localhost/foo%2fbar/?baz=1",
126 Header: map[string][]string{},
128 Host: "example.localhost",
129 RequestURI: "http://example.localhost/foo%2fbar/?baz=1",
133 Entry("when the request has a body", &httpUpstreamTableInput{
134 id: "requestWithBody",
135 serverAddr: &serverAddr,
136 target: "http://example.localhost/withBody",
138 body: []byte("body"),
140 expectedResponse: testHTTPResponse{
142 header: map[string][]string{
143 gapUpstream: {"requestWithBody"},
144 contentType: {applicationJSON},
146 request: testHTTPRequest{
148 URL: "http://example.localhost/withBody",
149 Header: map[string][]string{
150 contentLength: {"4"},
152 Body: []byte("body"),
153 Host: "example.localhost",
154 RequestURI: "http://example.localhost/withBody",
158 Entry("when the upstream is unavailable", &httpUpstreamTableInput{
159 id: "unavailableUpstream",
160 serverAddr: &invalidServer,
161 target: "http://example.localhost/unavailableUpstream",
165 expectedResponse: testHTTPResponse{
167 header: map[string][]string{
168 gapUpstream: {"unavailableUpstream"},
170 request: testHTTPRequest{},
173 Entry("when the upstream is unavailable and an error handler is set", &httpUpstreamTableInput{
174 id: "withErrorHandler",
175 serverAddr: &invalidServer,
176 target: "http://example.localhost/withErrorHandler",
179 errorHandler: func(rw http.ResponseWriter, _ *http.Request, _ error) {
181 rw.Write([]byte("error"))
183 expectedResponse: testHTTPResponse{
185 header: map[string][]string{
186 gapUpstream: {"withErrorHandler"},
189 request: testHTTPRequest{},
192 Entry("with a signature", &httpUpstreamTableInput{
194 serverAddr: &serverAddr,
195 target: "http://example.localhost/withSignature",
198 signatureData: &options.SignatureData{
203 expectedResponse: testHTTPResponse{
205 header: map[string][]string{
206 contentType: {applicationJSON},
207 gapUpstream: {"withSignature"},
209 request: testHTTPRequest{
211 URL: "http://example.localhost/withSignature",
212 Header: map[string][]string{
214 gapSignature: {"sha256 osMWI8Rr0Zr5HgNq6wakrgJITVJQMmFN1fXCesrqrmM="},
217 Host: "example.localhost",
218 RequestURI: "http://example.localhost/withSignature",
222 Entry("with existing headers", &httpUpstreamTableInput{
223 id: "existingHeaders",
224 serverAddr: &serverAddr,
225 target: "http://example.localhost/existingHeaders",
229 existingHeaders: map[string]string{
233 expectedResponse: testHTTPResponse{
235 header: map[string][]string{
236 gapUpstream: {"existingHeaders"},
237 contentType: {applicationJSON},
239 request: testHTTPRequest{
241 URL: "http://example.localhost/existingHeaders",
242 Header: map[string][]string{
243 "Header1": {"value1"},
244 "Header2": {"value2"},
247 Host: "example.localhost",
248 RequestURI: "http://example.localhost/existingHeaders",
254 It("ServeHTTP, when not passing a host header", func() {
255 req := httptest.NewRequest("", "http://example.localhost/foo", nil)
256 rw := httptest.NewRecorder()
258 flush := 1 * time.Second
259 upstream := options.Upstream{
261 PassHostHeader: false,
262 ProxyWebSockets: false,
263 InsecureSkipTLSVerify: false,
264 FlushInterval: &flush,
267 u, err := url.Parse(serverAddr)
268 Expect(err).ToNot(HaveOccurred())
270 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
271 httpUpstream, ok := handler.(*httpUpstreamProxy)
272 Expect(ok).To(BeTrue())
274 // Override the handler to just run the director and not actually send the request
275 requestInterceptor := func(h http.Handler) http.Handler {
276 return http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
277 proxy, ok := h.(*httputil.ReverseProxy)
278 Expect(ok).To(BeTrue())
282 httpUpstream.handler = requestInterceptor(httpUpstream.handler)
284 httpUpstream.ServeHTTP(rw, req)
285 Expect(req.Host).To(Equal(strings.TrimPrefix(serverAddr, "http://")))
288 type newUpstreamTableInput struct {
290 flushInterval time.Duration
292 sigData *options.SignatureData
293 errorHandler func(http.ResponseWriter, *http.Request, error)
296 DescribeTable("newHTTPUpstreamProxy",
297 func(in *newUpstreamTableInput) {
298 u, err := url.Parse("http://upstream:1234")
299 Expect(err).ToNot(HaveOccurred())
301 upstream := options.Upstream{
303 FlushInterval: &in.flushInterval,
304 InsecureSkipTLSVerify: in.skipVerify,
305 ProxyWebSockets: in.proxyWebSockets,
308 handler := newHTTPUpstreamProxy(upstream, u, in.sigData, in.errorHandler)
309 upstreamProxy, ok := handler.(*httpUpstreamProxy)
310 Expect(ok).To(BeTrue())
312 Expect(upstreamProxy.auth != nil).To(Equal(in.sigData != nil))
313 Expect(upstreamProxy.wsHandler != nil).To(Equal(in.proxyWebSockets))
314 Expect(upstreamProxy.upstream).To(Equal(upstream.ID))
315 Expect(upstreamProxy.handler).ToNot(BeNil())
317 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
318 Expect(ok).To(BeTrue())
319 Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
320 Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
322 Expect(proxy.Transport).To(Equal(&http.Transport{
323 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
327 Entry("with proxy websockets", &newUpstreamTableInput{
328 proxyWebSockets: true,
329 flushInterval: flushInterval1s,
334 Entry("with a non standard flush interval", &newUpstreamTableInput{
335 proxyWebSockets: false,
336 flushInterval: flushInterval5s,
341 Entry("with a InsecureSkipTLSVerify", &newUpstreamTableInput{
342 proxyWebSockets: false,
343 flushInterval: flushInterval1s,
348 Entry("with a SignatureData", &newUpstreamTableInput{
349 proxyWebSockets: false,
350 flushInterval: flushInterval1s,
352 sigData: &options.SignatureData{Hash: crypto.SHA256, Key: "secret"},
355 Entry("with an error handler", &newUpstreamTableInput{
356 proxyWebSockets: false,
357 flushInterval: flushInterval1s,
360 errorHandler: func(rw http.ResponseWriter, req *http.Request, arg3 error) {
366 Context("with a websocket proxy", func() {
367 var proxyServer *httptest.Server
370 flush := 1 * time.Second
371 upstream := options.Upstream{
372 ID: "websocketProxy",
373 PassHostHeader: true,
374 ProxyWebSockets: true,
375 InsecureSkipTLSVerify: false,
376 FlushInterval: &flush,
379 u, err := url.Parse(serverAddr)
380 Expect(err).ToNot(HaveOccurred())
382 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
383 proxyServer = httptest.NewServer(handler)
390 It("will proxy websockets", func() {
391 origin := "http://example.localhost"
392 message := "Hello, world!"
394 proxyURL, err := url.Parse(fmt.Sprintf("http://%s", proxyServer.Listener.Addr().String()))
395 Expect(err).ToNot(HaveOccurred())
397 wsAddr := fmt.Sprintf("ws://%s/", proxyURL.Host)
398 ws, err := websocket.Dial(wsAddr, "", origin)
399 Expect(err).ToNot(HaveOccurred())
401 Expect(websocket.Message.Send(ws, []byte(message))).To(Succeed())
402 var response testWebSocketResponse
403 Expect(websocket.JSON.Receive(ws, &response)).To(Succeed())
404 Expect(response).To(Equal(testWebSocketResponse{
410 It("will proxy HTTP requests", func() {
411 response, err := http.Get(fmt.Sprintf("http://%s", proxyServer.Listener.Addr().String()))
412 Expect(err).ToNot(HaveOccurred())
413 Expect(response.StatusCode).To(Equal(200))
414 Expect(response.Header.Get(gapUpstream)).To(Equal("websocketProxy"))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list