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