mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2024-11-24 08:52:25 +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:
parent
4816e87316
commit
70571d96e1
@ -13,6 +13,7 @@
|
||||
- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
|
||||
- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
|
||||
- [#2183](https://github.com/oauth2-proxy/oauth2-proxy/pull/2183) Allowing relative redirect url though an option
|
||||
- [#1866](https://github.com/oauth2-proxy/oauth2-proxy/pull/1866) Add support for unix socker as upstream (@babs)
|
||||
-
|
||||
# V7.5.1
|
||||
|
||||
|
@ -223,7 +223,11 @@ See below for provider specific options
|
||||
|
||||
### Upstreams Configuration
|
||||
|
||||
`oauth2-proxy` supports having multiple upstreams, and has the option to pass requests on to HTTP(S) servers or serve static files from the file system. HTTP and HTTPS upstreams are configured by providing a URL such as `http://127.0.0.1:8080/` for the upstream parameter. This will forward all authenticated requests to the upstream server. If you instead provide `http://127.0.0.1:8080/some/path/` then it will only be requests that start with `/some/path/` which are forwarded to the upstream.
|
||||
`oauth2-proxy` supports having multiple upstreams, and has the option to pass requests on to HTTP(S) servers, unix socket or serve static files from the file system.
|
||||
|
||||
HTTP and HTTPS upstreams are configured by providing a URL such as `http://127.0.0.1:8080/` for the upstream parameter. . This will forward all authenticated requests to the upstream server. If you instead provide `http://127.0.0.1:8080/some/path/` then it will only be requests that start with `/some/path/` which are forwarded to the upstream.
|
||||
|
||||
Unix socket upstreams are configured as `unix:///path/to/unix.sock`.
|
||||
|
||||
Static file paths are configured as a file:// URL. `file:///var/www/static/` will serve the files from that directory at `http://[oauth2-proxy url]/var/www/static/`, which may not be what you want. You can provide the path to where the files should be available by adding a fragment to the configured URL. The value of the fragment will then be used to specify which path the files are available at, e.g. `file:///var/www/static/#/static/` will make `/var/www/static/` available at `http://[oauth2-proxy url]/static/`.
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user