166 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
| package upstream
 | |
| 
 | |
| import (
 | |
| 	"crypto/tls"
 | |
| 	"net/http"
 | |
| 	"net/http/httputil"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/mbland/hmacauth"
 | |
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
 | |
| 	"github.com/yhat/wsutil"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// SignatureHeader is the name of the request header containing the GAP Signature
 | |
| 	// Part of hmacauth
 | |
| 	SignatureHeader = "GAP-Signature"
 | |
| 
 | |
| 	httpScheme  = "http"
 | |
| 	httpsScheme = "https"
 | |
| )
 | |
| 
 | |
| // SignatureHeaders contains the headers to be signed by the hmac algorithm
 | |
| // Part of hmacauth
 | |
| var SignatureHeaders = []string{
 | |
| 	"Content-Length",
 | |
| 	"Content-Md5",
 | |
| 	"Content-Type",
 | |
| 	"Date",
 | |
| 	"Authorization",
 | |
| 	"X-Forwarded-User",
 | |
| 	"X-Forwarded-Email",
 | |
| 	"X-Forwarded-Preferred-User",
 | |
| 	"X-Forwarded-Access-Token",
 | |
| 	"Cookie",
 | |
| 	"Gap-Auth",
 | |
| }
 | |
| 
 | |
| // newHTTPUpstreamProxy creates a new httpUpstreamProxy that can serve requests
 | |
| // to a single upstream host.
 | |
| func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
 | |
| 	// Set path to empty so that request paths start at the server root
 | |
| 	u.Path = ""
 | |
| 
 | |
| 	// Create a ReverseProxy
 | |
| 	proxy := newReverseProxy(u, upstream, errorHandler)
 | |
| 
 | |
| 	// Set up a WebSocket proxy if required
 | |
| 	var wsProxy http.Handler
 | |
| 	if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
 | |
| 		wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
 | |
| 	}
 | |
| 
 | |
| 	var auth hmacauth.HmacAuth
 | |
| 	if sigData != nil {
 | |
| 		auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key), SignatureHeader, SignatureHeaders)
 | |
| 	}
 | |
| 
 | |
| 	return &httpUpstreamProxy{
 | |
| 		upstream:  upstream.ID,
 | |
| 		handler:   proxy,
 | |
| 		wsHandler: wsProxy,
 | |
| 		auth:      auth,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // httpUpstreamProxy represents a single HTTP(S) upstream proxy
 | |
| type httpUpstreamProxy struct {
 | |
| 	upstream  string
 | |
| 	handler   http.Handler
 | |
| 	wsHandler http.Handler
 | |
| 	auth      hmacauth.HmacAuth
 | |
| }
 | |
| 
 | |
| // ServeHTTP proxies requests to the upstream provider while signing the
 | |
| // request headers
 | |
| func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 | |
| 	rw.Header().Set("GAP-Upstream-Address", h.upstream)
 | |
| 	if h.auth != nil {
 | |
| 		req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
 | |
| 		h.auth.SignRequest(req)
 | |
| 	}
 | |
| 	if h.wsHandler != nil && strings.EqualFold(req.Header.Get("Connection"), "upgrade") && req.Header.Get("Upgrade") == "websocket" {
 | |
| 		h.wsHandler.ServeHTTP(rw, req)
 | |
| 	} else {
 | |
| 		h.handler.ServeHTTP(rw, req)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // newReverseProxy creates a new reverse proxy for proxying requests to upstream
 | |
| // servers based on the upstream configuration provided.
 | |
| // The proxy should render an error page if there are failures connecting to the
 | |
| // upstream server.
 | |
| func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
 | |
| 	proxy := httputil.NewSingleHostReverseProxy(target)
 | |
| 
 | |
| 	// Configure options on the SingleHostReverseProxy
 | |
| 	if upstream.FlushInterval != nil {
 | |
| 		proxy.FlushInterval = upstream.FlushInterval.Duration()
 | |
| 	} else {
 | |
| 		proxy.FlushInterval = options.DefaultUpstreamFlushInterval
 | |
| 	}
 | |
| 
 | |
| 	// InsecureSkipVerify is a configurable option we allow
 | |
| 	/* #nosec G402 */
 | |
| 	if upstream.InsecureSkipTLSVerify {
 | |
| 		proxy.Transport = &http.Transport{
 | |
| 			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Set the request director based on the PassHostHeader option
 | |
| 	if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
 | |
| 		setProxyUpstreamHostHeader(proxy, target)
 | |
| 	} else {
 | |
| 		setProxyDirector(proxy)
 | |
| 	}
 | |
| 
 | |
| 	// Set the error handler so that upstream connection failures render the
 | |
| 	// error page instead of sending a empty response
 | |
| 	if errorHandler != nil {
 | |
| 		proxy.ErrorHandler = errorHandler
 | |
| 	}
 | |
| 	return proxy
 | |
| }
 | |
| 
 | |
| // setProxyUpstreamHostHeader sets the proxy.Director so that upstream requests
 | |
| // receive a host header matching the target URL.
 | |
| func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
 | |
| 	director := proxy.Director
 | |
| 	proxy.Director = func(req *http.Request) {
 | |
| 		director(req)
 | |
| 		// use RequestURI so that we aren't unescaping encoded slashes in the request path
 | |
| 		req.Host = target.Host
 | |
| 		req.URL.Opaque = req.RequestURI
 | |
| 		req.URL.RawQuery = ""
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // setProxyDirector sets the proxy.Director so that request URIs are escaped
 | |
| // when proxying to usptream servers.
 | |
| func setProxyDirector(proxy *httputil.ReverseProxy) {
 | |
| 	director := proxy.Director
 | |
| 	proxy.Director = func(req *http.Request) {
 | |
| 		director(req)
 | |
| 		// use RequestURI so that we aren't unescaping encoded slashes in the request path
 | |
| 		req.URL.Opaque = req.RequestURI
 | |
| 		req.URL.RawQuery = ""
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
 | |
| func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
 | |
| 	// This should create the correct scheme for insecure vs secure connections
 | |
| 	wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
 | |
| 	wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
 | |
| 
 | |
| 	wsProxy := wsutil.NewSingleHostReverseProxy(wsURL)
 | |
| 	/* #nosec G402 */
 | |
| 	if skipTLSVerify {
 | |
| 		wsProxy.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
 | |
| 	}
 | |
| 	return wsProxy
 | |
| }
 |