1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-02-15 14:03:45 +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) - [#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) - [#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 - [#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 # V7.5.1

View File

@ -223,7 +223,11 @@ See below for provider specific options
### Upstreams Configuration ### 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/`. 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.ProxyWebSockets = nil
upstream.FlushInterval = nil upstream.FlushInterval = nil
upstream.Timeout = nil upstream.Timeout = nil
case "unix":
upstream.Path = "/"
} }
upstreams.Upstreams = append(upstreams.Upstreams, upstream) upstreams.Upstreams = append(upstreams.Upstreams, upstream)

View File

@ -1,6 +1,8 @@
package upstream package upstream
import ( import (
"context"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
@ -18,6 +20,7 @@ const (
httpScheme = "http" httpScheme = "http"
httpsScheme = "https" httpsScheme = "https"
unixScheme = "unix"
) )
// SignatureHeaders contains the headers to be signed by the hmac algorithm // SignatureHeaders contains the headers to be signed by the hmac algorithm
@ -40,7 +43,10 @@ var SignatureHeaders = []string{
// to a single upstream host. // to a single upstream host.
func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler { 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 // 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 // Create a ReverseProxy
proxy := newReverseProxy(u, upstream, errorHandler) 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 // newReverseProxy creates a new reverse proxy for proxying requests to upstream
// servers based on the upstream configuration provided. // servers based on the upstream configuration provided.
// The proxy should render an error page if there are failures connecting to the // 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 // Inherit default transport options from Go's stdlib
transport := http.DefaultTransport.(*http.Transport).Clone() 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 // Change default duration for waiting for an upstream response
if upstream.Timeout != nil { if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration() transport.ResponseHeaderTimeout = upstream.Timeout.Duration()

View File

@ -312,6 +312,29 @@ var _ = Describe("HTTP Upstream Suite", func() {
}, },
expectedUpstream: "passExistingHostHeader", 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() { 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 { if err := m.registerFileServer(upstream, u, writer); err != nil {
return nil, fmt.Errorf("could not register file upstream %q: %v", upstream.ID, err) 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 { 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: default:
return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme) 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", RewriteTarget: "/double-match/rewrite/$1",
URI: serverAddr, URI: serverAddr,
}, },
{
ID: "unix-upstream",
Path: "/unix/",
URI: unixServerAddr,
},
} }
} }
@ -337,6 +342,27 @@ var _ = Describe("Proxy Suite", func() {
}, },
upstream: "", 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" "fmt"
"io" "io"
"log" "log"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -18,10 +19,12 @@ import (
) )
var ( var (
filesDir string filesDir string
server *httptest.Server server *httptest.Server
serverAddr string serverAddr string
invalidServer = "http://::1" unixServer *httptest.Server
unixServerAddr string
invalidServer = "http://::1"
) )
func TestUpstreamSuite(t *testing.T) { func TestUpstreamSuite(t *testing.T) {
@ -46,10 +49,17 @@ var _ = BeforeSuite(func() {
// Set up a webserver that reflects requests // Set up a webserver that reflects requests
server = httptest.NewServer(&testHTTPUpstream{}) server = httptest.NewServer(&testHTTPUpstream{})
serverAddr = fmt.Sprintf("http://%s", server.Listener.Addr().String()) 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() { var _ = AfterSuite(func() {
server.Close() server.Close()
unixServer.Close()
Expect(os.RemoveAll(filesDir)).To(Succeed()) Expect(os.RemoveAll(filesDir)).To(Succeed())
}) })

View File

@ -102,7 +102,7 @@ func validateUpstreamURI(upstream options.Upstream) []string {
} }
switch u.Scheme { switch u.Scheme {
case "http", "https", "file": case "http", "https", "file", "unix":
// Valid, do nothing // Valid, do nothing
default: default:
msgs = append(msgs, fmt.Sprintf("upstream %q has invalid scheme: %q", upstream.ID, u.Scheme)) msgs = append(msgs, fmt.Sprintf("upstream %q has invalid scheme: %q", upstream.ID, u.Scheme))