2020-05-26 20:53:10 +02:00
|
|
|
package upstream
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"net/http/httputil"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/mbland/hmacauth"
|
2021-03-06 19:27:16 +02:00
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
2020-09-29 18:44:42 +02:00
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
2020-05-26 20:53:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
2020-07-19 15:00:52 +02:00
|
|
|
if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
|
2020-05-26 20:53:10 +02:00
|
|
|
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) {
|
2021-03-06 19:27:16 +02:00
|
|
|
scope := middleware.GetRequestScope(req)
|
|
|
|
// If scope is nil, this will panic.
|
|
|
|
// A scope should always be injected before this handler is called.
|
|
|
|
scope.Upstream = h.upstream
|
|
|
|
|
|
|
|
// TODO (@NickMeves) - Deprecate GAP-Signature & remove GAP-Auth
|
2020-05-26 20:53:10 +02:00
|
|
|
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)
|
|
|
|
|
2022-05-13 22:36:21 +02:00
|
|
|
// Inherit default transport options from Go's stdlib
|
|
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
|
|
|
|
|
|
// Change default duration for waiting for an upstream response
|
|
|
|
if upstream.Timeout != nil {
|
|
|
|
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
|
|
|
|
}
|
|
|
|
|
2020-05-26 20:53:10 +02:00
|
|
|
// Configure options on the SingleHostReverseProxy
|
2020-05-27 16:13:57 +02:00
|
|
|
if upstream.FlushInterval != nil {
|
2020-11-12 01:02:00 +02:00
|
|
|
proxy.FlushInterval = upstream.FlushInterval.Duration()
|
2020-05-27 16:13:57 +02:00
|
|
|
} else {
|
2020-11-19 12:35:04 +02:00
|
|
|
proxy.FlushInterval = options.DefaultUpstreamFlushInterval
|
2020-05-27 16:13:57 +02:00
|
|
|
}
|
|
|
|
|
2020-07-21 03:49:45 +02:00
|
|
|
// InsecureSkipVerify is a configurable option we allow
|
2020-07-20 07:24:18 +02:00
|
|
|
/* #nosec G402 */
|
2020-05-26 20:53:10 +02:00
|
|
|
if upstream.InsecureSkipTLSVerify {
|
2022-05-13 22:36:21 +02:00
|
|
|
transport.TLSClientConfig.InsecureSkipVerify = true
|
2020-05-26 20:53:10 +02:00
|
|
|
}
|
|
|
|
|
2021-03-21 20:14:10 +02:00
|
|
|
// Ensure we always pass the original request path
|
|
|
|
setProxyDirector(proxy)
|
|
|
|
|
2020-07-19 15:00:52 +02:00
|
|
|
if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
|
2020-05-26 20:53:10 +02:00
|
|
|
setProxyUpstreamHostHeader(proxy, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2022-05-13 22:36:21 +02:00
|
|
|
|
|
|
|
// Apply the customized transport to our proxy before returning it
|
|
|
|
proxy.Transport = transport
|
|
|
|
|
2020-05-26 20:53:10 +02:00
|
|
|
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)
|
|
|
|
req.Host = target.Host
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 = ""
|
2021-03-21 13:10:12 +02:00
|
|
|
req.URL.ForceQuery = false
|
2020-05-26 20:53:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
|
|
|
|
func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
|
2021-10-03 16:38:40 +02:00
|
|
|
wsProxy := httputil.NewSingleHostReverseProxy(u)
|
2022-05-13 22:36:21 +02:00
|
|
|
|
|
|
|
// Inherit default transport options from Go's stdlib
|
|
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
|
|
|
2020-07-20 07:24:18 +02:00
|
|
|
/* #nosec G402 */
|
2020-05-26 20:53:10 +02:00
|
|
|
if skipTLSVerify {
|
2022-05-13 22:36:21 +02:00
|
|
|
transport.TLSClientConfig.InsecureSkipVerify = true
|
2020-05-26 20:53:10 +02:00
|
|
|
}
|
2022-05-13 22:36:21 +02:00
|
|
|
|
|
|
|
// Apply the customized transport to our proxy before returning it
|
|
|
|
wsProxy.Transport = transport
|
|
|
|
|
2020-05-26 20:53:10 +02:00
|
|
|
return wsProxy
|
|
|
|
}
|