From 1295f87b33448bafdd67acfef625424d11304a52 Mon Sep 17 00:00:00 2001 From: Christian Groschupp Date: Wed, 18 Sep 2019 22:40:33 +0200 Subject: [PATCH 1/5] Add static upstream --- docs/configuration/configuration.md | 2 +- main.go | 2 +- oauthproxy.go | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index c076dc88..ffe1be42 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -105,7 +105,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example | `-standard-logging-format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) | | `-tls-cert-file` | string | path to certificate file | | | `-tls-key-file` | string | path to private key file | | -| `-upstream` | string \| list | the http url(s) of the upstream endpoint or `file://` paths for static files. Routing is based on the path | | +| `-upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://` for static response. Routing is based on the path | | | `-validate-url` | string | Access token validation endpoint | | | `-version` | n/a | print version string | | | `-whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (eg `.example.com`) | | diff --git a/main.go b/main.go index a4bf378e..d30d6bda 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { flagSet.String("tls-key-file", "", "path to private key file") flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)") - flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path") + flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint, file:// paths for static files or static:// for static response. Routing is based on the path") flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") diff --git a/oauthproxy.go b/oauthproxy.go index 01c18c39..429775d1 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -12,6 +12,7 @@ import ( "net/http/httputil" "net/url" "regexp" + "strconv" "strings" "time" @@ -207,6 +208,16 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { proxy := NewWebSocketOrRestReverseProxy(u, opts, auth) serveMux.Handle(path, proxy) + case "static": + serveMux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { + responseCode, err := strconv.Atoi(u.Host) + if err != nil { + logger.Printf("unable to convert %q to int, use default \"200\"", u.Host) + responseCode = 200 + } + rw.WriteHeader(responseCode) + fmt.Fprintf(rw, "Authenticated") + }) case "file": if u.Fragment != "" { path = u.Fragment From dc36836800b0749f78e6ec5854e08926ef8a6830 Mon Sep 17 00:00:00 2001 From: Christian Groschupp Date: Thu, 19 Sep 2019 11:03:38 +0200 Subject: [PATCH 2/5] Add tests for static upstream --- oauthproxy.go | 8 +++---- oauthproxy_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 429775d1..66fad491 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -202,17 +202,17 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { } for _, u := range opts.proxyURLs { path := u.Path + host := u.Host switch u.Scheme { case httpScheme, httpsScheme: logger.Printf("mapping path %q => upstream %q", path, u) proxy := NewWebSocketOrRestReverseProxy(u, opts, auth) serveMux.Handle(path, proxy) - case "static": - serveMux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - responseCode, err := strconv.Atoi(u.Host) + serveMux.HandleFunc(path, func(rw http.ResponseWriter, req *http.Request) { + responseCode, err := strconv.Atoi(host) if err != nil { - logger.Printf("unable to convert %q to int, use default \"200\"", u.Host) + logger.Printf("unable to convert %q to int, use default \"200\"", host) responseCode = 200 } rw.WriteHeader(responseCode) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 8dd3adfb..563e6cef 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -365,6 +365,7 @@ type PassAccessTokenTest struct { type PassAccessTokenTestOptions struct { PassAccessToken bool + ProxyUpstream string } func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest { @@ -372,7 +373,6 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes t.providerServer = httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger.Printf("%#v", r) var payload string switch r.URL.Path { case "/oauth/token": @@ -389,6 +389,9 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes t.opts = NewOptions() t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL) + if opts.ProxyUpstream != "" { + t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream) + } // The CookieSecret must be 32 bytes in order to create the AES // cipher. t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" @@ -459,6 +462,39 @@ func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int return rw.Code, rw.Body.String() } +func (patTest *PassAccessTokenTest) getProxyEndpoint(cookie string) (httpCode int, accessToken string) { + cookieName := patTest.proxy.CookieName + var value string + keyPrefix := cookieName + "=" + + for _, field := range strings.Split(cookie, "; ") { + value = strings.TrimPrefix(field, keyPrefix) + if value != field { + break + } else { + value = "" + } + } + if value == "" { + return 0, "" + } + + req, err := http.NewRequest("GET", "/static-proxy", strings.NewReader("")) + if err != nil { + return 0, "" + } + req.AddCookie(&http.Cookie{ + Name: cookieName, + Value: value, + Path: "/", + Expires: time.Now().Add(time.Duration(24)), + HttpOnly: true, + }) + rw := httptest.NewRecorder() + patTest.proxy.ServeHTTP(rw, req) + return rw.Code, rw.Body.String() +} + func TestForwardAccessTokenUpstream(t *testing.T) { patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{ PassAccessToken: true, @@ -482,6 +518,28 @@ func TestForwardAccessTokenUpstream(t *testing.T) { assert.Equal(t, "my_auth_token", payload) } +func TestStaticProxyUpstream(t *testing.T) { + patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{ + PassAccessToken: true, + ProxyUpstream: "static://200/static-proxy", + }) + + defer patTest.Close() + + // A successful validation will redirect and set the auth cookie. + code, cookie := patTest.getCallbackEndpoint() + if code != 302 { + t.Fatalf("expected 302; got %d", code) + } + assert.NotEqual(t, nil, cookie) + + code, payload := patTest.getProxyEndpoint(cookie) + if code != 200 { + t.Fatalf("expected 200; got %d", code) + } + assert.Equal(t, "Authenticated", payload) +} + func TestDoNotForwardAccessTokenUpstream(t *testing.T) { patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{ PassAccessToken: false, From a46ee952a65e93358fe10731a8ffc89c627dd83d Mon Sep 17 00:00:00 2001 From: Christian Groschupp Date: Fri, 4 Oct 2019 15:07:31 +0200 Subject: [PATCH 3/5] Move responceCode out of HandleFunc. --- oauthproxy.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 66fad491..9a45866a 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -209,12 +209,13 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { proxy := NewWebSocketOrRestReverseProxy(u, opts, auth) serveMux.Handle(path, proxy) case "static": + responseCode, err := strconv.Atoi(host) + if err != nil { + logger.Printf("unable to convert %q to int, use default \"200\"", host) + responseCode = 200 + } + serveMux.HandleFunc(path, func(rw http.ResponseWriter, req *http.Request) { - responseCode, err := strconv.Atoi(host) - if err != nil { - logger.Printf("unable to convert %q to int, use default \"200\"", host) - responseCode = 200 - } rw.WriteHeader(responseCode) fmt.Fprintf(rw, "Authenticated") }) From 3d17159c5c641a19f798e8ff9fdfd203dbafcd4c Mon Sep 17 00:00:00 2001 From: Christian Groschupp Date: Fri, 4 Oct 2019 15:39:31 +0200 Subject: [PATCH 4/5] replace getRootEndpoint by getEndpointWithCookie --- oauthproxy_test.go | 47 +++++++++------------------------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 563e6cef..f64c6b18 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -428,7 +428,9 @@ func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, return rw.Code, rw.HeaderMap["Set-Cookie"][1] } -func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int, accessToken string) { +// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath +// and cookie and returns body and status code. +func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) { cookieName := patTest.proxy.CookieName var value string keyPrefix := cookieName + "=" @@ -445,7 +447,7 @@ func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int return 0, "" } - req, err := http.NewRequest("GET", "/", strings.NewReader("")) + req, err := http.NewRequest("GET", endpoint, strings.NewReader("")) if err != nil { return 0, "" } @@ -462,39 +464,6 @@ func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int return rw.Code, rw.Body.String() } -func (patTest *PassAccessTokenTest) getProxyEndpoint(cookie string) (httpCode int, accessToken string) { - cookieName := patTest.proxy.CookieName - var value string - keyPrefix := cookieName + "=" - - for _, field := range strings.Split(cookie, "; ") { - value = strings.TrimPrefix(field, keyPrefix) - if value != field { - break - } else { - value = "" - } - } - if value == "" { - return 0, "" - } - - req, err := http.NewRequest("GET", "/static-proxy", strings.NewReader("")) - if err != nil { - return 0, "" - } - req.AddCookie(&http.Cookie{ - Name: cookieName, - Value: value, - Path: "/", - Expires: time.Now().Add(time.Duration(24)), - HttpOnly: true, - }) - rw := httptest.NewRecorder() - patTest.proxy.ServeHTTP(rw, req) - return rw.Code, rw.Body.String() -} - func TestForwardAccessTokenUpstream(t *testing.T) { patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{ PassAccessToken: true, @@ -511,7 +480,7 @@ func TestForwardAccessTokenUpstream(t *testing.T) { // Now we make a regular request; the access_token from the cookie is // forwarded as the "X-Forwarded-Access-Token" header. The token is // read by the test provider server and written in the response body. - code, payload := patTest.getRootEndpoint(cookie) + code, payload := patTest.getEndpointWithCookie(cookie, "/") if code != 200 { t.Fatalf("expected 200; got %d", code) } @@ -533,7 +502,9 @@ func TestStaticProxyUpstream(t *testing.T) { } assert.NotEqual(t, nil, cookie) - code, payload := patTest.getProxyEndpoint(cookie) + // Now we make a regular request againts the upstream proxy; And validate + // the returned status code through the static proxy. + code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy") if code != 200 { t.Fatalf("expected 200; got %d", code) } @@ -555,7 +526,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) { // Now we make a regular request, but the access token header should // not be present. - code, payload := patTest.getRootEndpoint(cookie) + code, payload := patTest.getEndpointWithCookie(cookie, "/") if code != 200 { t.Fatalf("expected 200; got %d", code) } From f570fb9f58e48e383ff52e1637026183f9674e10 Mon Sep 17 00:00:00 2001 From: Christian Groschupp Date: Thu, 10 Oct 2019 10:15:04 +0200 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d76e8958..7687dd3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka) - [#273](https://github.com/pusher/oauth2_proxy/pull/273) Support Go 1.13 (@dio) - [#275](https://github.com/pusher/oauth2_proxy/pull/275) docker: build from debian buster (@syscll) +- [#265](https://github.com/pusher/oauth2_proxy/pull/265) Add upstream with static response (@cgroschupp) # v4.0.0