1
0
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:
Damien Degois 2023-10-26 11:57:00 +02:00 committed by GitHub
parent 4816e87316
commit 70571d96e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 9 deletions

View File

@ -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

View File

@ -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/`.

View File

@ -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)

View File

@ -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()

View File

@ -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() {

View File

@ -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)

View File

@ -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",
}),
)
})

View File

@ -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())
})

View File

@ -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))