git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add support for unix socket as upstream (#1866)
70571d9Damien Degois
CHANGELOG.md+1
@@ -13,6 +13,7 @@
13- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
13- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
14- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
14- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
15- [#2183](https://github.com/oauth2-proxy/oauth2-proxy/pull/2183) Allowing relative redirect url though an option
15- [#2183](https://github.com/oauth2-proxy/oauth2-proxy/pull/2183) Allowing relative redirect url though an option
16+- [#1866](https://github.com/oauth2-proxy/oauth2-proxy/pull/1866) Add support for unix socker as upstream (@babs)
16-
17-
17# V7.5.1
18# V7.5.1
18
19
docs/docs/configuration/overview.md+5−1
@@ -223,7 +223,11 @@ See below for provider specific options
223
223
224### Upstreams Configuration
224### Upstreams Configuration
225
225
226+`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.
227+
226−`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.
228+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.
229+
230+Unix socket upstreams are configured as `unix:///path/to/unix.sock`.
227
231
228Static 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/`.
232Static 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/`.
229
233
pkg/apis/options/legacy_options.go+2
@@ -176,6 +176,8 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
176 upstream.ProxyWebSockets = nil
176 upstream.ProxyWebSockets = nil
177 upstream.FlushInterval = nil
177 upstream.FlushInterval = nil
178 upstream.Timeout = nil
178 upstream.Timeout = nil
179+ case "unix":
180+ upstream.Path = "/"
179 }
181 }
180
182
181 upstreams.Upstreams = append(upstreams.Upstreams, upstream)
183 upstreams.Upstreams = append(upstreams.Upstreams, upstream)
pkg/upstream/http.go+34−1
@@ -1,6 +1,8 @@
1package upstream
1package upstream
2
2
3import (
3import (
4+ "context"
5+ "net"
4 "net/http"
6 "net/http"
5 "net/http/httputil"
7 "net/http/httputil"
6 "net/url"
8 "net/url"
@@ -18,6 +20,7 @@ const (
18
20
19 httpScheme = "http"
21 httpScheme = "http"
20 httpsScheme = "https"
22 httpsScheme = "https"
23+ unixScheme = "unix"
21)
24)
22
25
23// SignatureHeaders contains the headers to be signed by the hmac algorithm
26// SignatureHeaders contains the headers to be signed by the hmac algorithm
@@ -40,7 +43,10 @@ var SignatureHeaders = []string{
40// to a single upstream host.
43// to a single upstream host.
41func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
44func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
42 // Set path to empty so that request paths start at the server root
45 // Set path to empty so that request paths start at the server root
46+ // Unix scheme need the path to find the socket
47+ if u.Scheme != "unix" {
43− u.Path = ""
48+ u.Path = ""
49+ }
44
50
45 // Create a ReverseProxy
51 // Create a ReverseProxy
46 proxy := newReverseProxy(u, upstream, errorHandler)
52 proxy := newReverseProxy(u, upstream, errorHandler)
@@ -92,6 +98,25 @@ func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
92 }
98 }
93}
99}
94
100
101+// Unix implementation of http.RoundTripper, required to register unix protocol in reverse proxy
102+type unixRoundTripper struct {
103+ Transport *http.Transport
104+}
105+
106+// Implementation of https://pkg.go.dev/net/http#RoundTripper interface to support http protocol over unix socket
107+func (t *unixRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
108+ // Inspired by https://github.com/tv42/httpunix
109+ // Not having a Host, even if not used, makes the reverseproxy fail with a "no Host in request URL"
110+ if req.Host == "" {
111+ req.Host = "localhost"
112+ }
113+ req.URL.Host = req.Host
114+ tt := t.Transport
115+ req = req.Clone(req.Context())
116+ req.URL.Scheme = "http"
117+ return tt.RoundTrip(req)
118+}
119+
95// newReverseProxy creates a new reverse proxy for proxying requests to upstream
120// newReverseProxy creates a new reverse proxy for proxying requests to upstream
96// servers based on the upstream configuration provided.
121// servers based on the upstream configuration provided.
97// The proxy should render an error page if there are failures connecting to the
122// 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
102 // Inherit default transport options from Go's stdlib
127 // Inherit default transport options from Go's stdlib
103 transport := http.DefaultTransport.(*http.Transport).Clone()
128 transport := http.DefaultTransport.(*http.Transport).Clone()
104
129
130+ if target.Scheme == "unix" {
131+ transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
132+ dialer := net.Dialer{}
133+ return dialer.DialContext(ctx, target.Scheme, target.Path)
134+ }
135+ transport.RegisterProtocol(target.Scheme, &unixRoundTripper{Transport: transport})
136+ }
137+
105 // Change default duration for waiting for an upstream response
138 // Change default duration for waiting for an upstream response
106 if upstream.Timeout != nil {
139 if upstream.Timeout != nil {
107 transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
140 transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
pkg/upstream/http_test.go+23
@@ -312,6 +312,29 @@ var _ = Describe("HTTP Upstream Suite", func() {
312 },
312 },
313 expectedUpstream: "passExistingHostHeader",
313 expectedUpstream: "passExistingHostHeader",
314 }),
314 }),
315+ Entry("request using UNIX socket upstream", &httpUpstreamTableInput{
316+ id: "unix-upstream",
317+ serverAddr: &unixServerAddr,
318+ target: "http://example.localhost/file",
319+ method: "GET",
320+ body: []byte{},
321+ errorHandler: nil,
322+ expectedResponse: testHTTPResponse{
323+ code: 200,
324+ header: map[string][]string{
325+ contentType: {applicationJSON},
326+ },
327+ request: testHTTPRequest{
328+ Method: "GET",
329+ URL: "http://example.localhost/file",
330+ Header: map[string][]string{},
331+ Body: []byte{},
332+ Host: "example.localhost",
333+ RequestURI: "http://example.localhost/file",
334+ },
335+ },
336+ expectedUpstream: "unix-upstream",
337+ }),
315 )
338 )
316
339
317 It("ServeHTTP, when not passing a host header", func() {
340 It("ServeHTTP, when not passing a host header", func() {
pkg/upstream/proxy.go+2−2
@@ -48,9 +48,9 @@ func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData,
48 if err := m.registerFileServer(upstream, u, writer); err != nil {
48 if err := m.registerFileServer(upstream, u, writer); err != nil {
49 return nil, fmt.Errorf("could not register file upstream %q: %v", upstream.ID, err)
49 return nil, fmt.Errorf("could not register file upstream %q: %v", upstream.ID, err)
50 }
50 }
51− case httpScheme, httpsScheme:
51+ case httpScheme, httpsScheme, unixScheme:
52 if err := m.registerHTTPUpstreamProxy(upstream, u, sigData, writer); err != nil {
52 if err := m.registerHTTPUpstreamProxy(upstream, u, sigData, writer); err != nil {
53− return nil, fmt.Errorf("could not register HTTP upstream %q: %v", upstream.ID, err)
53+ return nil, fmt.Errorf("could not register %s upstream %q: %v", u.Scheme, upstream.ID, err)
54 }
54 }
55 default:
55 default:
56 return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
56 return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
pkg/upstream/proxy_test.go+26
@@ -98,6 +98,11 @@ var _ = Describe("Proxy Suite", func() {
98 RewriteTarget: "/double-match/rewrite/$1",
98 RewriteTarget: "/double-match/rewrite/$1",
99 URI: serverAddr,
99 URI: serverAddr,
100 },
100 },
101+ {
102+ ID: "unix-upstream",
103+ Path: "/unix/",
104+ URI: unixServerAddr,
105+ },
101 }
106 }
102 }
107 }
103
108
@@ -337,6 +342,27 @@ var _ = Describe("Proxy Suite", func() {
337 },
342 },
338 upstream: "",
343 upstream: "",
339 }),
344 }),
345+ Entry("with a request to the UNIX socket backend", &proxyTableInput{
346+ target: "http://example.localhost/unix/file",
347+ response: testHTTPResponse{
348+ code: 200,
349+ header: map[string][]string{
350+ contentType: {applicationJSON},
351+ },
352+ request: testHTTPRequest{
353+ Method: "GET",
354+ URL: "http://example.localhost/unix/file",
355+ Header: map[string][]string{
356+ "Gap-Auth": {""},
357+ "Gap-Signature": {"sha256 4ux8esLj2fw9sTWZwgFhb00bGbw0Fnhed5Fm9jz5Blw="},
358+ },
359+ Body: []byte{},
360+ Host: "example.localhost",
361+ RequestURI: "http://example.localhost/unix/file",
362+ },
363+ },
364+ upstream: "unix-upstream",
365+ }),
340 )
366 )
341 })
367 })
342
368
pkg/upstream/upstream_suite_test.go+14−4
@@ -5,6 +5,7 @@ import (
5 "fmt"
5 "fmt"
6 "io"
6 "io"
7 "log"
7 "log"
8+ "net"
8 "net/http"
9 "net/http"
9 "net/http/httptest"
10 "net/http/httptest"
10 "os"
11 "os"
@@ -18,10 +19,12 @@ import (
18)
19)
19
20
20var (
21var (
21− filesDir string
22+ filesDir string
22− server *httptest.Server
23+ server *httptest.Server
23− serverAddr string
24+ serverAddr string
25+ unixServer *httptest.Server
26+ unixServerAddr string
24− invalidServer = "http://::1"
27+ invalidServer = "http://::1"
25)
28)
26
29
27func TestUpstreamSuite(t *testing.T) {
30func TestUpstreamSuite(t *testing.T) {
@@ -46,10 +49,17 @@ var _ = BeforeSuite(func() {
46 // Set up a webserver that reflects requests
49 // Set up a webserver that reflects requests
47 server = httptest.NewServer(&testHTTPUpstream{})
50 server = httptest.NewServer(&testHTTPUpstream{})
48 serverAddr = fmt.Sprintf("http://%s", server.Listener.Addr().String())
51 serverAddr = fmt.Sprintf("http://%s", server.Listener.Addr().String())
52+
53+ unixServer = httptest.NewUnstartedServer(&testHTTPUpstream{})
54+ unixListener, _ := net.Listen("unix", path.Join(filesDir, "test.sock"))
55+ unixServer.Listener = unixListener
56+ unixServer.Start()
57+ unixServerAddr = fmt.Sprintf("unix://%s", path.Join(filesDir, "test.sock"))
49})
58})
50
59
51var _ = AfterSuite(func() {
60var _ = AfterSuite(func() {
52 server.Close()
61 server.Close()
62+ unixServer.Close()
53 Expect(os.RemoveAll(filesDir)).To(Succeed())
63 Expect(os.RemoveAll(filesDir)).To(Succeed())
54})
64})
55
65
pkg/validation/upstreams.go+1−1
@@ -102,7 +102,7 @@ func validateUpstreamURI(upstream options.Upstream) []string {
102 }
102 }
103
103
104 switch u.Scheme {
104 switch u.Scheme {
105− case "http", "https", "file":
105+ case "http", "https", "file", "unix":
106 // Valid, do nothing
106 // Valid, do nothing
107 default:
107 default:
108 msgs = append(msgs, fmt.Sprintf("upstream %q has invalid scheme: %q", upstream.ID, u.Scheme))
108 msgs = append(msgs, fmt.Sprintf("upstream %q has invalid scheme: %q", upstream.ID, u.Scheme))