1package upstream
2
3import (
4	"crypto/tls"
5	"net/http"
6	"net/http/httputil"
7	"net/url"
8	"strings"
9
10	"github.com/mbland/hmacauth"
11	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
12	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
13)
14
15const (
16	// SignatureHeader is the name of the request header containing the GAP Signature
17	// Part of hmacauth
18	SignatureHeader = "GAP-Signature"
19
20	httpScheme  = "http"
21	httpsScheme = "https"
22)
23
24// SignatureHeaders contains the headers to be signed by the hmac algorithm
25// Part of hmacauth
26var SignatureHeaders = []string{
27	"Content-Length",
28	"Content-Md5",
29	"Content-Type",
30	"Date",
31	"Authorization",
32	"X-Forwarded-User",
33	"X-Forwarded-Email",
34	"X-Forwarded-Preferred-User",
35	"X-Forwarded-Access-Token",
36	"Cookie",
37	"Gap-Auth",
38}
39
40// newHTTPUpstreamProxy creates a new httpUpstreamProxy that can serve requests
41// to a single upstream host.
42func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
43	// Set path to empty so that request paths start at the server root
44	u.Path = ""
45
46	// Create a ReverseProxy
47	proxy := newReverseProxy(u, upstream, errorHandler)
48
49	// Set up a WebSocket proxy if required
50	var wsProxy http.Handler
51	if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
52		wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
53	}
54
55	var auth hmacauth.HmacAuth
56	if sigData != nil {
57		auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key), SignatureHeader, SignatureHeaders)
58	}
59
60	return &httpUpstreamProxy{
61		upstream:  upstream.ID,
62		handler:   proxy,
63		wsHandler: wsProxy,
64		auth:      auth,
65	}
66}
67
68// httpUpstreamProxy represents a single HTTP(S) upstream proxy
69type httpUpstreamProxy struct {
70	upstream  string
71	handler   http.Handler
72	wsHandler http.Handler
73	auth      hmacauth.HmacAuth
74}
75
76// ServeHTTP proxies requests to the upstream provider while signing the
77// request headers
78func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
79	scope := middleware.GetRequestScope(req)
80	// If scope is nil, this will panic.
81	// A scope should always be injected before this handler is called.
82	scope.Upstream = h.upstream
83
84	// TODO (@NickMeves) - Deprecate GAP-Signature & remove GAP-Auth
85	if h.auth != nil {
86		req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
87		h.auth.SignRequest(req)
88	}
89	if h.wsHandler != nil && strings.EqualFold(req.Header.Get("Connection"), "upgrade") && req.Header.Get("Upgrade") == "websocket" {
90		h.wsHandler.ServeHTTP(rw, req)
91	} else {
92		h.handler.ServeHTTP(rw, req)
93	}
94}
95
96// newReverseProxy creates a new reverse proxy for proxying requests to upstream
97// servers based on the upstream configuration provided.
98// The proxy should render an error page if there are failures connecting to the
99// upstream server.
100func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
101	proxy := httputil.NewSingleHostReverseProxy(target)
102
103	// Configure options on the SingleHostReverseProxy
104	if upstream.FlushInterval != nil {
105		proxy.FlushInterval = upstream.FlushInterval.Duration()
106	} else {
107		proxy.FlushInterval = options.DefaultUpstreamFlushInterval
108	}
109
110	// InsecureSkipVerify is a configurable option we allow
111	/* #nosec G402 */
112	if upstream.InsecureSkipTLSVerify {
113		proxy.Transport = &http.Transport{
114			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
115		}
116	}
117
118	// Ensure we always pass the original request path
119	setProxyDirector(proxy)
120
121	if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
122		setProxyUpstreamHostHeader(proxy, target)
123	}
124
125	// Set the error handler so that upstream connection failures render the
126	// error page instead of sending a empty response
127	if errorHandler != nil {
128		proxy.ErrorHandler = errorHandler
129	}
130	return proxy
131}
132
133// setProxyUpstreamHostHeader sets the proxy.Director so that upstream requests
134// receive a host header matching the target URL.
135func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
136	director := proxy.Director
137	proxy.Director = func(req *http.Request) {
138		director(req)
139		req.Host = target.Host
140	}
141}
142
143// setProxyDirector sets the proxy.Director so that request URIs are escaped
144// when proxying to usptream servers.
145func setProxyDirector(proxy *httputil.ReverseProxy) {
146	director := proxy.Director
147	proxy.Director = func(req *http.Request) {
148		director(req)
149		// use RequestURI so that we aren't unescaping encoded slashes in the request path
150		req.URL.Opaque = req.RequestURI
151		req.URL.RawQuery = ""
152		req.URL.ForceQuery = false
153	}
154}
155
156// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
157func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
158	wsProxy := httputil.NewSingleHostReverseProxy(u)
159	/* #nosec G402 */
160	if skipTLSVerify {
161		wsProxy.Transport = &http.Transport{
162			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
163		}
164	}
165	return wsProxy
166}
167