@@ -0,0 +1,157 @@
1+package upstream
2+
3+import (
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/pkg/apis/options"
12+ "github.com/yhat/wsutil"
13+)
14+
15+const (
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
26+var 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.
42+func 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 {
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
69+type 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
78+func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
79+ rw.Header().Set("GAP-Upstream-Address", h.upstream)
80+ if h.auth != nil {
81+ req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
82+ h.auth.SignRequest(req)
83+ }
84+ if h.wsHandler != nil && strings.EqualFold(req.Header.Get("Connection"), "upgrade") && req.Header.Get("Upgrade") == "websocket" {
85+ h.wsHandler.ServeHTTP(rw, req)
86+ } else {
87+ h.handler.ServeHTTP(rw, req)
88+ }
89+}
90+
91+// newReverseProxy creates a new reverse proxy for proxying requests to upstream
92+// servers based on the upstream configuration provided.
93+// The proxy should render an error page if there are failures connecting to the
94+// upstream server.
95+func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
96+ proxy := httputil.NewSingleHostReverseProxy(target)
97+
98+ // Configure options on the SingleHostReverseProxy
99+ proxy.FlushInterval = *upstream.FlushInterval
100+ if upstream.InsecureSkipTLSVerify {
101+ proxy.Transport = &http.Transport{
102+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
103+ }
104+ }
105+
106+ // Set the request director based on the PassHostHeader option
107+ if !upstream.PassHostHeader {
108+ setProxyUpstreamHostHeader(proxy, target)
109+ } else {
110+ setProxyDirector(proxy)
111+ }
112+
113+ // Set the error handler so that upstream connection failures render the
114+ // error page instead of sending a empty response
115+ if errorHandler != nil {
116+ proxy.ErrorHandler = errorHandler
117+ }
118+ return proxy
119+}
120+
121+// setProxyUpstreamHostHeader sets the proxy.Director so that upstream requests
122+// receive a host header matching the target URL.
123+func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
124+ director := proxy.Director
125+ proxy.Director = func(req *http.Request) {
126+ director(req)
127+ // use RequestURI so that we aren't unescaping encoded slashes in the request path
128+ req.Host = target.Host
129+ req.URL.Opaque = req.RequestURI
130+ req.URL.RawQuery = ""
131+ }
132+}
133+
134+// setProxyDirector sets the proxy.Director so that request URIs are escaped
135+// when proxying to usptream servers.
136+func setProxyDirector(proxy *httputil.ReverseProxy) {
137+ director := proxy.Director
138+ proxy.Director = func(req *http.Request) {
139+ director(req)
140+ // use RequestURI so that we aren't unescaping encoded slashes in the request path
141+ req.URL.Opaque = req.RequestURI
142+ req.URL.RawQuery = ""
143+ }
144+}
145+
146+// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
147+func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
148+ // This should create the correct scheme for insecure vs secure connections
149+ wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
150+ wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
151+
152+ wsProxy := wsutil.NewSingleHostReverseProxy(wsURL)
153+ if skipTLSVerify {
154+ wsProxy.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
155+ }
156+ return wsProxy
157+}