1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2024-11-24 08:52:25 +02:00
oauth2-proxy/pkg/upstream/http.go
Damien Degois 70571d96e1
Add support for unix socket as upstream (#1866)
* Add support for unix socket as upstream

* Add CHANGELOG.md entry

* Add Unix socket documentation

* Don't export unixRoundTripper, switch from string prefix to Scheme match

* Add basic unix server mock

* Add some tests and comments
2023-10-26 10:57:00 +01:00

215 lines
6.4 KiB
Go

package upstream
import (
"context"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/mbland/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
)
const (
// SignatureHeader is the name of the request header containing the GAP Signature
// Part of hmacauth
SignatureHeader = "GAP-Signature"
httpScheme = "http"
httpsScheme = "https"
unixScheme = "unix"
)
// 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
// Unix scheme need the path to find the socket
if u.Scheme != "unix" {
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) {
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
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)
}
}
// Unix implementation of http.RoundTripper, required to register unix protocol in reverse proxy
type unixRoundTripper struct {
Transport *http.Transport
}
// Implementation of https://pkg.go.dev/net/http#RoundTripper interface to support http protocol over unix socket
func (t *unixRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Inspired by https://github.com/tv42/httpunix
// Not having a Host, even if not used, makes the reverseproxy fail with a "no Host in request URL"
if req.Host == "" {
req.Host = "localhost"
}
req.URL.Host = req.Host
tt := t.Transport
req = req.Clone(req.Context())
req.URL.Scheme = "http"
return tt.RoundTrip(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)
// Inherit default transport options from Go's stdlib
transport := http.DefaultTransport.(*http.Transport).Clone()
if target.Scheme == "unix" {
transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, target.Scheme, target.Path)
}
transport.RegisterProtocol(target.Scheme, &unixRoundTripper{Transport: transport})
}
// Change default duration for waiting for an upstream response
if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
}
// 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 {
transport.TLSClientConfig.InsecureSkipVerify = true
}
// Ensure we always pass the original request path
setProxyDirector(proxy)
if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
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
}
// Apply the customized transport to our proxy before returning it
proxy.Transport = transport
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 = ""
req.URL.ForceQuery = false
}
}
// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
wsProxy := httputil.NewSingleHostReverseProxy(u)
// Inherit default transport options from Go's stdlib
transport := http.DefaultTransport.(*http.Transport).Clone()
/* #nosec G402 */
if skipTLSVerify {
transport.TLSClientConfig.InsecureSkipVerify = true
}
// Apply the customized transport to our proxy before returning it
wsProxy.Transport = transport
return wsProxy
}