Add upstream package with Proxy server implementation
This commit is contained in:
		
							parent
							
								
									b6b5194190
								
							
						
					
					
						commit
						e1c3e938cc
					
				| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					package upstream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fileScheme = "file"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// newFileServer creates a new fileServer that can serve requests
 | 
				
			||||||
 | 
					// to a file system location.
 | 
				
			||||||
 | 
					func newFileServer(id, path, fileSystemPath string) http.Handler {
 | 
				
			||||||
 | 
						return &fileServer{
 | 
				
			||||||
 | 
							upstream: id,
 | 
				
			||||||
 | 
							handler:  newFileServerForPath(path, fileSystemPath),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// newFileServerForPath creates a http.Handler to serve files from the filesystem
 | 
				
			||||||
 | 
					func newFileServerForPath(path string, filesystemPath string) http.Handler {
 | 
				
			||||||
 | 
						return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fileServer represents a single filesystem upstream proxy
 | 
				
			||||||
 | 
					type fileServer struct {
 | 
				
			||||||
 | 
						upstream string
 | 
				
			||||||
 | 
						handler  http.Handler
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ServeHTTP proxies requests to the upstream provider while signing the
 | 
				
			||||||
 | 
					// request headers
 | 
				
			||||||
 | 
					func (u *fileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						rw.Header().Set("GAP-Upstream-Address", u.upstream)
 | 
				
			||||||
 | 
						u.handler.ServeHTTP(rw, req)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,157 @@
 | 
				
			||||||
 | 
					package upstream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/http/httputil"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/mbland/hmacauth"
 | 
				
			||||||
 | 
						"github.com/oauth2-proxy/oauth2-proxy/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 {
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
						proxy.FlushInterval = *upstream.FlushInterval
 | 
				
			||||||
 | 
						if upstream.InsecureSkipTLSVerify {
 | 
				
			||||||
 | 
							proxy.Transport = &http.Transport{
 | 
				
			||||||
 | 
								TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set the request director based on the PassHostHeader option
 | 
				
			||||||
 | 
						if !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)
 | 
				
			||||||
 | 
						if skipTLSVerify {
 | 
				
			||||||
 | 
							wsProxy.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return wsProxy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					package upstream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"html/template"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
 | 
				
			||||||
 | 
						"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ProxyErrorHandler is a function that will be used to render error pages when
 | 
				
			||||||
 | 
					// HTTP proxies fail to connect to upstream servers.
 | 
				
			||||||
 | 
					type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
 | 
				
			||||||
 | 
					// multiple upstreams.
 | 
				
			||||||
 | 
					func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, errorHandler ProxyErrorHandler) (http.Handler, error) {
 | 
				
			||||||
 | 
						m := &multiUpstreamProxy{
 | 
				
			||||||
 | 
							serveMux: http.NewServeMux(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, upstream := range upstreams {
 | 
				
			||||||
 | 
							if upstream.Static {
 | 
				
			||||||
 | 
								m.registerStaticResponseHandler(upstream)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							u, err := url.Parse(upstream.URI)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("error parsing URI for upstream %q: %w", upstream.ID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							switch u.Scheme {
 | 
				
			||||||
 | 
							case fileScheme:
 | 
				
			||||||
 | 
								m.registerFileServer(upstream, u)
 | 
				
			||||||
 | 
							case httpScheme, httpsScheme:
 | 
				
			||||||
 | 
								m.registerHTTPUpstreamProxy(upstream, u, sigData, errorHandler)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return m, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// multiUpstreamProxy will serve requests directed to multiple upstream servers
 | 
				
			||||||
 | 
					// registered in the serverMux.
 | 
				
			||||||
 | 
					type multiUpstreamProxy struct {
 | 
				
			||||||
 | 
						serveMux *http.ServeMux
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ServerHTTP handles HTTP requests.
 | 
				
			||||||
 | 
					func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						m.serveMux.ServeHTTP(rw, req)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerStaticResponseHandler registers a static response handler with at the given path.
 | 
				
			||||||
 | 
					func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream) {
 | 
				
			||||||
 | 
						m.serveMux.Handle(upstream.Path, newStaticResponseHandler(upstream.ID, upstream.StaticCode))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerFileServer registers a new fileServer based on the configuration given.
 | 
				
			||||||
 | 
					func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *url.URL) {
 | 
				
			||||||
 | 
						logger.Printf("mapping path %q => file system %q", upstream.Path, u.Path)
 | 
				
			||||||
 | 
						m.serveMux.Handle(upstream.Path, newFileServer(upstream.ID, upstream.Path, u.Path))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
 | 
				
			||||||
 | 
					func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) {
 | 
				
			||||||
 | 
						logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
 | 
				
			||||||
 | 
						m.serveMux.Handle(upstream.Path, newHTTPUpstreamProxy(upstream, u, sigData, errorHandler))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewProxyErrorHandler creates a ProxyErrorHandler using the template given.
 | 
				
			||||||
 | 
					func NewProxyErrorHandler(errorTemplate *template.Template, proxyPrefix string) ProxyErrorHandler {
 | 
				
			||||||
 | 
						return func(rw http.ResponseWriter, req *http.Request, proxyErr error) {
 | 
				
			||||||
 | 
							logger.Printf("Error proxying to upstream server: %v", proxyErr)
 | 
				
			||||||
 | 
							rw.WriteHeader(http.StatusBadGateway)
 | 
				
			||||||
 | 
							data := struct {
 | 
				
			||||||
 | 
								Title       string
 | 
				
			||||||
 | 
								Message     string
 | 
				
			||||||
 | 
								ProxyPrefix string
 | 
				
			||||||
 | 
							}{
 | 
				
			||||||
 | 
								Title:       "Bad Gateway",
 | 
				
			||||||
 | 
								Message:     "Error proxying to upstream server",
 | 
				
			||||||
 | 
								ProxyPrefix: proxyPrefix,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							errorTemplate.Execute(rw, data)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					package upstream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultStaticResponseCode = 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// newStaticResponseHandler creates a new staticResponseHandler that serves a
 | 
				
			||||||
 | 
					// a static response code.
 | 
				
			||||||
 | 
					func newStaticResponseHandler(upstream string, code *int) http.Handler {
 | 
				
			||||||
 | 
						if code == nil {
 | 
				
			||||||
 | 
							c := defaultStaticResponseCode
 | 
				
			||||||
 | 
							code = &c
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &staticResponseHandler{
 | 
				
			||||||
 | 
							code:     *code,
 | 
				
			||||||
 | 
							upstream: upstream,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// staticResponseHandler responds with a static response with the given response code.
 | 
				
			||||||
 | 
					type staticResponseHandler struct {
 | 
				
			||||||
 | 
						code     int
 | 
				
			||||||
 | 
						upstream string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ServeHTTP serves a static response.
 | 
				
			||||||
 | 
					func (s *staticResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						rw.Header().Set("GAP-Upstream-Address", s.upstream)
 | 
				
			||||||
 | 
						rw.WriteHeader(s.code)
 | 
				
			||||||
 | 
						fmt.Fprintf(rw, "Authenticated")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue