mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-18 04:41:19 +02:00
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
This commit is contained in:
@@ -176,6 +176,8 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
|
||||
upstream.ProxyWebSockets = nil
|
||||
upstream.FlushInterval = nil
|
||||
upstream.Timeout = nil
|
||||
case "unix":
|
||||
upstream.Path = "/"
|
||||
}
|
||||
|
||||
upstreams.Upstreams = append(upstreams.Upstreams, upstream)
|
||||
|
||||
+34
-1
@@ -1,6 +1,8 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
@@ -18,6 +20,7 @@ const (
|
||||
|
||||
httpScheme = "http"
|
||||
httpsScheme = "https"
|
||||
unixScheme = "unix"
|
||||
)
|
||||
|
||||
// SignatureHeaders contains the headers to be signed by the hmac algorithm
|
||||
@@ -40,7 +43,10 @@ var SignatureHeaders = []string{
|
||||
// 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 = ""
|
||||
// Unix scheme need the path to find the socket
|
||||
if u.Scheme != "unix" {
|
||||
u.Path = ""
|
||||
}
|
||||
|
||||
// Create a ReverseProxy
|
||||
proxy := newReverseProxy(u, upstream, errorHandler)
|
||||
@@ -92,6 +98,25 @@ func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -102,6 +127,14 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
|
||||
// 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()
|
||||
|
||||
@@ -312,6 +312,29 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||
},
|
||||
expectedUpstream: "passExistingHostHeader",
|
||||
}),
|
||||
Entry("request using UNIX socket upstream", &httpUpstreamTableInput{
|
||||
id: "unix-upstream",
|
||||
serverAddr: &unixServerAddr,
|
||||
target: "http://example.localhost/file",
|
||||
method: "GET",
|
||||
body: []byte{},
|
||||
errorHandler: nil,
|
||||
expectedResponse: testHTTPResponse{
|
||||
code: 200,
|
||||
header: map[string][]string{
|
||||
contentType: {applicationJSON},
|
||||
},
|
||||
request: testHTTPRequest{
|
||||
Method: "GET",
|
||||
URL: "http://example.localhost/file",
|
||||
Header: map[string][]string{},
|
||||
Body: []byte{},
|
||||
Host: "example.localhost",
|
||||
RequestURI: "http://example.localhost/file",
|
||||
},
|
||||
},
|
||||
expectedUpstream: "unix-upstream",
|
||||
}),
|
||||
)
|
||||
|
||||
It("ServeHTTP, when not passing a host header", func() {
|
||||
|
||||
@@ -48,9 +48,9 @@ func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData,
|
||||
if err := m.registerFileServer(upstream, u, writer); err != nil {
|
||||
return nil, fmt.Errorf("could not register file upstream %q: %v", upstream.ID, err)
|
||||
}
|
||||
case httpScheme, httpsScheme:
|
||||
case httpScheme, httpsScheme, unixScheme:
|
||||
if err := m.registerHTTPUpstreamProxy(upstream, u, sigData, writer); err != nil {
|
||||
return nil, fmt.Errorf("could not register HTTP upstream %q: %v", upstream.ID, err)
|
||||
return nil, fmt.Errorf("could not register %s upstream %q: %v", u.Scheme, upstream.ID, err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
|
||||
|
||||
@@ -98,6 +98,11 @@ var _ = Describe("Proxy Suite", func() {
|
||||
RewriteTarget: "/double-match/rewrite/$1",
|
||||
URI: serverAddr,
|
||||
},
|
||||
{
|
||||
ID: "unix-upstream",
|
||||
Path: "/unix/",
|
||||
URI: unixServerAddr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +342,27 @@ var _ = Describe("Proxy Suite", func() {
|
||||
},
|
||||
upstream: "",
|
||||
}),
|
||||
Entry("with a request to the UNIX socket backend", &proxyTableInput{
|
||||
target: "http://example.localhost/unix/file",
|
||||
response: testHTTPResponse{
|
||||
code: 200,
|
||||
header: map[string][]string{
|
||||
contentType: {applicationJSON},
|
||||
},
|
||||
request: testHTTPRequest{
|
||||
Method: "GET",
|
||||
URL: "http://example.localhost/unix/file",
|
||||
Header: map[string][]string{
|
||||
"Gap-Auth": {""},
|
||||
"Gap-Signature": {"sha256 4ux8esLj2fw9sTWZwgFhb00bGbw0Fnhed5Fm9jz5Blw="},
|
||||
},
|
||||
Body: []byte{},
|
||||
Host: "example.localhost",
|
||||
RequestURI: "http://example.localhost/unix/file",
|
||||
},
|
||||
},
|
||||
upstream: "unix-upstream",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -18,10 +19,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
filesDir string
|
||||
server *httptest.Server
|
||||
serverAddr string
|
||||
invalidServer = "http://::1"
|
||||
filesDir string
|
||||
server *httptest.Server
|
||||
serverAddr string
|
||||
unixServer *httptest.Server
|
||||
unixServerAddr string
|
||||
invalidServer = "http://::1"
|
||||
)
|
||||
|
||||
func TestUpstreamSuite(t *testing.T) {
|
||||
@@ -46,10 +49,17 @@ var _ = BeforeSuite(func() {
|
||||
// Set up a webserver that reflects requests
|
||||
server = httptest.NewServer(&testHTTPUpstream{})
|
||||
serverAddr = fmt.Sprintf("http://%s", server.Listener.Addr().String())
|
||||
|
||||
unixServer = httptest.NewUnstartedServer(&testHTTPUpstream{})
|
||||
unixListener, _ := net.Listen("unix", path.Join(filesDir, "test.sock"))
|
||||
unixServer.Listener = unixListener
|
||||
unixServer.Start()
|
||||
unixServerAddr = fmt.Sprintf("unix://%s", path.Join(filesDir, "test.sock"))
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
server.Close()
|
||||
unixServer.Close()
|
||||
Expect(os.RemoveAll(filesDir)).To(Succeed())
|
||||
})
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ func validateUpstreamURI(upstream options.Upstream) []string {
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http", "https", "file":
|
||||
case "http", "https", "file", "unix":
|
||||
// Valid, do nothing
|
||||
default:
|
||||
msgs = append(msgs, fmt.Sprintf("upstream %q has invalid scheme: %q", upstream.ID, u.Scheme))
|
||||
|
||||
Reference in New Issue
Block a user