git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge remote-tracking branch 'upstream/master' into helm-example

# Conflicts: # CHANGELOG.md

commite8fce0b14d83f189f55f9b40704f9c5ecc81e184 authorEvgeni Gordeev <egordeev@conserviscorp.com> dateFri 19 Jun 2020, 22:25 (6 years ago) parent84360115817028
14 files changed+230−260
CHANGELOG.md+2 go.mod+1 go.sum+3 http.go+7−39 http_test.go−99 logging_handler.go+1−4 logging_handler_test.go+13−76 main.go+21−5 oauthproxy.go+3−30 pkg/middleware/healthcheck.go+48 pkg/middleware/healthcheck_test.go+112 pkg/middleware/middleware_suite_test.go+16 pkg/validation/logging.go+2−6 pkg/validation/options.go+1−1
CHANGELOG.md+2
@@ -56,6 +56,8 @@
5656## Changes since v5.1.1
5757
5858- [#615](https://github.com/oauth2-proxy/oauth2-proxy/pull/615) Kubernetes example based on Kind cluster and Nginx ingress (@EvgeniGordeev)
59+- [#620](https://github.com/oauth2-proxy/oauth2-proxy/pull/620) Add HealthCheck middleware (@JoelSpeed)
60+- [#597](https://github.com/oauth2-proxy/oauth2-proxy/pull/597) Don't log invalid redirect if redirect is empty (@JoelSpeed)
5961- [#604](https://github.com/oauth2-proxy/oauth2-proxy/pull/604) Add Keycloak local testing environment (@EvgeniGordeev)
6062- [#539](https://github.com/oauth2-proxy/oauth2-proxy/pull/539) Refactor encryption ciphers and add AES-GCM support (@NickMeves)
6163- [#601](https://github.com/oauth2-proxy/oauth2-proxy/pull/601) Ensure decrypted user/email are valid UTF8 (@JoelSpeed)
go.mod+1
@@ -12,6 +12,7 @@ require (
1212 github.com/dgrijalva/jwt-go v3.2.0+incompatible
1313 github.com/fsnotify/fsnotify v1.4.9
1414 github.com/go-redis/redis/v7 v7.2.0
15+ github.com/justinas/alice v1.2.0
1516 github.com/kr/pretty v0.2.0 // indirect
1617 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
1718 github.com/mitchellh/mapstructure v1.1.2
go.sum+3
@@ -102,6 +102,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
102102github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
103103github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
104104github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
105+github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
106+github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
105107github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
106108github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
107109github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -129,6 +131,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
129131github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
130132github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
131133github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
134+github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ=
132135github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
133136github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
134137github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
http.go+7−39
@@ -9,6 +9,7 @@ import (
99 "strings"
1010 "time"
1111
12+ "github.com/justinas/alice"
1213 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
1314 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
1415)
@@ -29,45 +30,6 @@ func (s *Server) ListenAndServe() {
2930 }
3031}
3132
32−// Used with gcpHealthcheck()
33−const userAgentHeader = "User-Agent"
34−const googleHealthCheckUserAgent = "GoogleHC/1.0"
35−const rootPath = "/"
36−
37−// gcpHealthcheck handles healthcheck queries from GCP.
38−func gcpHealthcheck(h http.Handler) http.Handler {
39− return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40− // Check for liveness and readiness: used for Google App Engine
41− if r.URL.EscapedPath() == "/liveness_check" {
42− w.WriteHeader(http.StatusOK)
43− w.Write([]byte("OK"))
44− return
45− }
46− if r.URL.EscapedPath() == "/readiness_check" {
47− w.WriteHeader(http.StatusOK)
48− w.Write([]byte("OK"))
49− return
50− }
51−
52− // Check for GKE ingress healthcheck: The ingress requires the root
53− // path of the target to return a 200 (OK) to indicate the service's good health. This can be quite a challenging demand
54− // depending on the application's path structure. This middleware filters out the requests from the health check by
55− //
56− // 1. checking that the request path is indeed the root path
57− // 2. ensuring that the User-Agent is "GoogleHC/1.0", the health checker
58− // 3. ensuring the request method is "GET"
59− if r.URL.Path == rootPath &&
60− r.Header.Get(userAgentHeader) == googleHealthCheckUserAgent &&
61− r.Method == http.MethodGet {
62−
63− w.WriteHeader(http.StatusOK)
64− return
65− }
66−
67− h.ServeHTTP(w, r)
68− })
69−}
70−
7133// ServeHTTP constructs a net.Listener and starts handling HTTP requests
7234func (s *Server) ServeHTTP() {
7335 HTTPAddress := s.Opts.HTTPAddress
@@ -168,6 +130,12 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
168130 return tc, nil
169131}
170132
133+func newRedirectToHTTPS(opts *options.Options) alice.Constructor {
134+ return func(next http.Handler) http.Handler {
135+ return redirectToHTTPS(opts, next)
136+ }
137+}
138+
171139func redirectToHTTPS(opts *options.Options, h http.Handler) http.Handler {
172140 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
173141 proto := r.Header.Get("X-Forwarded-Proto")
http_test.go−99
@@ -11,105 +11,6 @@ import (
1111 "github.com/stretchr/testify/assert"
1212)
1313
14−const localhost = "127.0.0.1"
15−const host = "test-server"
16−
17−func TestGCPHealthcheckLiveness(t *testing.T) {
18− handler := func(w http.ResponseWriter, req *http.Request) {
19− w.Write([]byte("test"))
20− }
21−
22− h := gcpHealthcheck(http.HandlerFunc(handler))
23− rw := httptest.NewRecorder()
24− r, _ := http.NewRequest("GET", "/liveness_check", nil)
25− r.RemoteAddr = localhost
26− r.Host = host
27− h.ServeHTTP(rw, r)
28−
29− assert.Equal(t, 200, rw.Code)
30− assert.Equal(t, "OK", rw.Body.String())
31−}
32−
33−func TestGCPHealthcheckReadiness(t *testing.T) {
34− handler := func(w http.ResponseWriter, req *http.Request) {
35− w.Write([]byte("test"))
36− }
37−
38− h := gcpHealthcheck(http.HandlerFunc(handler))
39− rw := httptest.NewRecorder()
40− r, _ := http.NewRequest("GET", "/readiness_check", nil)
41− r.RemoteAddr = localhost
42− r.Host = host
43− h.ServeHTTP(rw, r)
44−
45− assert.Equal(t, 200, rw.Code)
46− assert.Equal(t, "OK", rw.Body.String())
47−}
48−
49−func TestGCPHealthcheckNotHealthcheck(t *testing.T) {
50− handler := func(w http.ResponseWriter, req *http.Request) {
51− w.Write([]byte("test"))
52− }
53−
54− h := gcpHealthcheck(http.HandlerFunc(handler))
55− rw := httptest.NewRecorder()
56− r, _ := http.NewRequest("GET", "/not_any_check", nil)
57− r.RemoteAddr = localhost
58− r.Host = host
59− h.ServeHTTP(rw, r)
60−
61− assert.Equal(t, "test", rw.Body.String())
62−}
63−
64−func TestGCPHealthcheckIngress(t *testing.T) {
65− handler := func(w http.ResponseWriter, req *http.Request) {
66− w.Write([]byte("test"))
67− }
68−
69− h := gcpHealthcheck(http.HandlerFunc(handler))
70− rw := httptest.NewRecorder()
71− r, _ := http.NewRequest("GET", "/", nil)
72− r.RemoteAddr = localhost
73− r.Host = host
74− r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
75− h.ServeHTTP(rw, r)
76−
77− assert.Equal(t, 200, rw.Code)
78− assert.Equal(t, "", rw.Body.String())
79−}
80−
81−func TestGCPHealthcheckNotIngress(t *testing.T) {
82− handler := func(w http.ResponseWriter, req *http.Request) {
83− w.Write([]byte("test"))
84− }
85−
86− h := gcpHealthcheck(http.HandlerFunc(handler))
87− rw := httptest.NewRecorder()
88− r, _ := http.NewRequest("GET", "/foo", nil)
89− r.RemoteAddr = localhost
90− r.Host = host
91− r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
92− h.ServeHTTP(rw, r)
93−
94− assert.Equal(t, "test", rw.Body.String())
95−}
96−
97−func TestGCPHealthcheckNotIngressPut(t *testing.T) {
98− handler := func(w http.ResponseWriter, req *http.Request) {
99− w.Write([]byte("test"))
100− }
101−
102− h := gcpHealthcheck(http.HandlerFunc(handler))
103− rw := httptest.NewRecorder()
104− r, _ := http.NewRequest("PUT", "/", nil)
105− r.RemoteAddr = localhost
106− r.Host = host
107− r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
108− h.ServeHTTP(rw, r)
109−
110− assert.Equal(t, "test", rw.Body.String())
111−}
112−
11314func TestRedirectToHTTPSTrue(t *testing.T) {
11415 opts := options.NewOptions()
11516 opts.ForceHTTPS = true
logging_handler.go+1−4
@@ -21,7 +21,6 @@ type responseLogger struct {
2121 size int
2222 upstream string
2323 authInfo string
24− silent bool
2524}
2625
2726// Header returns the ResponseWriter's Header
@@ -105,7 +104,5 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
105104 url := *req.URL
106105 responseLogger := &responseLogger{w: w}
107106 h.handler.ServeHTTP(responseLogger, req)
108− if !responseLogger.silent {
109− logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
110− }
107+ logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
111108}
logging_handler_test.go+13−76
@@ -5,14 +5,11 @@ import (
55 "fmt"
66 "net/http"
77 "net/http/httptest"
8− "net/url"
98 "strings"
109 "testing"
1110 "time"
1211
13− "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
1412 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
15− "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
1613)
1714
1815func TestLoggingHandler_ServeHTTP(t *testing.T) {
@@ -22,21 +19,20 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
2219 Format,
2320 ExpectedLogMessage,
2421 Path string
25− ExcludePaths []string
26− SilencePingLogging bool
22+ ExcludePaths []string
2723 }{
28− {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{}, false},
29− {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{}, true},
30− {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{"/ping"}, false},
31− {logger.DefaultRequestLoggingFormat, "", "/foo/bar", []string{"/foo/bar"}, false},
32− {logger.DefaultRequestLoggingFormat, "", "/ping", []string{}, true},
33− {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}, false},
34− {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}, true},
35− {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/foo/bar", "/ping"}, false},
36− {"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{}, true},
37− {"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{"/ping"}, false},
38− {"{{.RequestMethod}}", "GET\n", "/ping", []string{}, false},
39− {"{{.RequestMethod}}", "", "/ping", []string{"/ping"}, true},
24+ {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{}},
25+ {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{}},
26+ {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{"/ping"}},
27+ {logger.DefaultRequestLoggingFormat, "", "/foo/bar", []string{"/foo/bar"}},
28+ {logger.DefaultRequestLoggingFormat, "", "/ping", []string{}},
29+ {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}},
30+ {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}},
31+ {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/foo/bar", "/ping"}},
32+ {"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{}},
33+ {"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{"/ping"}},
34+ {"{{.RequestMethod}}", "GET\n", "/ping", []string{}},
35+ {"{{.RequestMethod}}", "", "/ping", []string{"/ping"}},
4036 }
4137
4238 for _, test := range tests {
@@ -52,9 +48,6 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
5248
5349 logger.SetOutput(buf)
5450 logger.SetReqTemplate(test.Format)
55− if test.SilencePingLogging {
56− test.ExcludePaths = append(test.ExcludePaths, "/ping")
57− }
5851 logger.SetExcludePaths(test.ExcludePaths)
5952 h := LoggingHandler(http.HandlerFunc(handler))
6053
@@ -70,59 +63,3 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
7063 }
7164 }
7265}
73−
74−func TestLoggingHandler_PingUserAgent(t *testing.T) {
75− tests := []struct {
76− ExpectedLogMessage string
77− Path string
78− SilencePingLogging bool
79− WithUserAgent string
80− }{
81− {"444\n", "/foo", true, "Blah"},
82− {"444\n", "/foo", false, "Blah"},
83− {"", "/ping", true, "Blah"},
84− {"200\n", "/ping", false, "Blah"},
85− {"", "/ping", true, "PingMe!"},
86− {"", "/ping", false, "PingMe!"},
87− {"", "/foo", true, "PingMe!"},
88− {"", "/foo", false, "PingMe!"},
89− }
90−
91− for idx, test := range tests {
92− t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
93− opts := options.NewOptions()
94− opts.PingUserAgent = "PingMe!"
95− opts.SkipAuthRegex = []string{"/foo"}
96− opts.Upstreams = []string{"static://444/foo"}
97− opts.Logging.SilencePing = test.SilencePingLogging
98− if test.SilencePingLogging {
99− opts.Logging.ExcludePaths = []string{"/ping"}
100− }
101− opts.RawRedirectURL = "localhost"
102− validation.Validate(opts)
103−
104− p := NewOAuthProxy(opts, func(email string) bool {
105− return true
106− })
107− p.provider = NewTestProvider(&url.URL{Host: "localhost"}, "")
108−
109− buf := bytes.NewBuffer(nil)
110− logger.SetOutput(buf)
111− logger.SetReqEnabled(true)
112− logger.SetReqTemplate("{{.StatusCode}}")
113−
114− r, _ := http.NewRequest("GET", test.Path, nil)
115− if test.WithUserAgent != "" {
116− r.Header.Set("User-Agent", test.WithUserAgent)
117− }
118−
119− h := LoggingHandler(p)
120− h.ServeHTTP(httptest.NewRecorder(), r)
121−
122− actual := buf.String()
123− if !strings.Contains(actual, test.ExpectedLogMessage) {
124− t.Errorf("Log message was\n%s\ninstead of matching \n%s", actual, test.ExpectedLogMessage)
125− }
126− })
127− }
128−}
main.go+21−5
@@ -3,7 +3,6 @@ package main
33import (
44 "fmt"
55 "math/rand"
6− "net/http"
76 "os"
87 "os/signal"
98 "runtime"
@@ -11,8 +10,10 @@ import (
1110 "syscall"
1211 "time"
1312
13+ "github.com/justinas/alice"
1414 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
1515 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
16+ "github.com/oauth2-proxy/oauth2-proxy/pkg/middleware"
1617 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
1718)
1819
@@ -71,14 +72,29 @@ func main() {
7172
7273 rand.Seed(time.Now().UnixNano())
7374
74− var handler http.Handler
75+ chain := alice.New()
76+
77+ if opts.ForceHTTPS {
78+ chain = chain.Append(newRedirectToHTTPS(opts))
79+ }
80+
81+ healthCheckPaths := []string{opts.PingPath}
82+ healthCheckUserAgents := []string{opts.PingUserAgent}
7583 if opts.GCPHealthChecks {
76− handler = redirectToHTTPS(opts, gcpHealthcheck(LoggingHandler(oauthproxy)))
84+ healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check")
85+ healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0")
86+ }
87+
88+ // To silence logging of health checks, register the health check handler before
89+ // the logging handler
90+ if opts.Logging.SilencePing {
91+ chain = chain.Append(middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents), LoggingHandler)
7792 } else {
78− handler = redirectToHTTPS(opts, LoggingHandler(oauthproxy))
93+ chain = chain.Append(LoggingHandler, middleware.NewHealthCheck(healthCheckPaths, healthCheckUserAgents))
7994 }
95+
8096 s := &Server{
81− Handler: handler,
97+ Handler: chain.Then(oauthproxy),
8298 Opts: opts,
8399 stop: make(chan struct{}, 1),
84100 }
oauthproxy.go+3−30
@@ -81,9 +81,6 @@ type OAuthProxy struct {
8181 Validator func(string) bool
8282
8383 RobotsPath string
84− PingPath string
85− PingUserAgent string
86− SilencePings bool
8784 SignInPath string
8885 SignOutPath string
8986 OAuthStartPath string
@@ -313,9 +310,6 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) *OAuthPro
313310 Validator: validator,
314311
315312 RobotsPath: "/robots.txt",
316− PingPath: opts.PingPath,
317− PingUserAgent: opts.PingUserAgent,
318− SilencePings: opts.Logging.SilencePing,
319313 SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
320314 SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
321315 OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
@@ -468,17 +462,6 @@ func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
468462 fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
469463}
470464
471−// PingPage responds 200 OK to requests
472−func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
473− if p.SilencePings {
474− if rl, ok := rw.(*responseLogger); ok {
475− rl.silent = true
476− }
477− }
478− rw.WriteHeader(http.StatusOK)
479− fmt.Fprintf(rw, "OK")
480−}
481−
482465// ErrorPage writes an error response
483466func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
484467 rw.WriteHeader(code)
@@ -615,6 +598,9 @@ func validOptionalPort(port string) bool {
615598// IsValidRedirect checks whether the redirect URL is whitelisted
616599func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
617600 switch {
601+ case redirect == "":
602+ // The user didn't specify a redirect, should fallback to `/`
603+ return false
618604 case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !invalidRedirectRegex.MatchString(redirect):
619605 return true
620606 case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
@@ -684,17 +670,6 @@ func prepareNoCache(w http.ResponseWriter) {
684670 }
685671}
686672
687−// IsPingRequest will check if the request appears to be performing a health check
688−// either via the path it's requesting or by a special User-Agent configuration.
689−func (p *OAuthProxy) IsPingRequest(req *http.Request) bool {
690−
691− if req.URL.EscapedPath() == p.PingPath {
692− return true
693− }
694−
695− return p.PingUserAgent != "" && req.Header.Get("User-Agent") == p.PingUserAgent
696−}
697−
698673func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
699674 if strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
700675 prepareNoCache(rw)
@@ -703,8 +678,6 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
703678 switch path := req.URL.Path; {
704679 case path == p.RobotsPath:
705680 p.RobotsTxt(rw)
706− case p.IsPingRequest(req):
707− p.PingPage(rw)
708681 case p.IsWhitelistedRequest(req):
709682 p.serveMux.ServeHTTP(rw, req)
710683 case path == p.SignInPath:
pkg/middleware/healthcheck.gonew file+48
@@ -0,0 +1,48 @@
1+package middleware
2+
3+import (
4+ "fmt"
5+ "net/http"
6+
7+ "github.com/justinas/alice"
8+)
9+
10+func NewHealthCheck(paths, userAgents []string) alice.Constructor {
11+ return func(next http.Handler) http.Handler {
12+ return healthCheck(paths, userAgents, next)
13+ }
14+}
15+
16+func healthCheck(paths, userAgents []string, next http.Handler) http.Handler {
17+ // Use a map as a set to check health check paths
18+ pathSet := make(map[string]struct{})
19+ for _, path := range paths {
20+ pathSet[path] = struct{}{}
21+ }
22+
23+ // Use a map as a set to check health check paths
24+ userAgentSet := make(map[string]struct{})
25+ for _, userAgent := range userAgents {
26+ userAgentSet[userAgent] = struct{}{}
27+ }
28+
29+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
30+ if isHealthCheckRequest(pathSet, userAgentSet, req) {
31+ rw.WriteHeader(http.StatusOK)
32+ fmt.Fprintf(rw, "OK")
33+ return
34+ }
35+
36+ next.ServeHTTP(rw, req)
37+ })
38+}
39+
40+func isHealthCheckRequest(paths, userAgents map[string]struct{}, req *http.Request) bool {
41+ if _, ok := paths[req.URL.EscapedPath()]; ok {
42+ return true
43+ }
44+ if _, ok := userAgents[req.Header.Get("User-Agent")]; ok {
45+ return true
46+ }
47+ return false
48+}
pkg/middleware/healthcheck_test.gonew file+112
@@ -0,0 +1,112 @@
1+package middleware
2+
3+import (
4+ "net/http"
5+ "net/http/httptest"
6+
7+ . "github.com/onsi/ginkgo"
8+ . "github.com/onsi/ginkgo/extensions/table"
9+ . "github.com/onsi/gomega"
10+)
11+
12+var _ = Describe("HealthCheck suite", func() {
13+ type requestTableInput struct {
14+ healthCheckPaths []string
15+ healthCheckUserAgents []string
16+ requestString string
17+ headers map[string]string
18+ expectedStatus int
19+ expectedBody string
20+ }
21+
22+ DescribeTable("when serving a request",
23+ func(in *requestTableInput) {
24+ req := httptest.NewRequest("", in.requestString, nil)
25+ for k, v := range in.headers {
26+ req.Header.Add(k, v)
27+ }
28+
29+ rw := httptest.NewRecorder()
30+
31+ handler := NewHealthCheck(in.healthCheckPaths, in.healthCheckUserAgents)(http.NotFoundHandler())
32+ handler.ServeHTTP(rw, req)
33+
34+ Expect(rw.Code).To(Equal(in.expectedStatus))
35+ Expect(rw.Body.String()).To(Equal(in.expectedBody))
36+ },
37+ Entry("when requesting the healthcheck path", &requestTableInput{
38+ healthCheckPaths: []string{"/ping"},
39+ healthCheckUserAgents: []string{"hc/1.0"},
40+ requestString: "http://example.com/ping",
41+ headers: map[string]string{},
42+ expectedStatus: 200,
43+ expectedBody: "OK",
44+ }),
45+ Entry("when requesting a different path", &requestTableInput{
46+ healthCheckPaths: []string{"/ping"},
47+ healthCheckUserAgents: []string{"hc/1.0"},
48+ requestString: "http://example.com/different",
49+ headers: map[string]string{},
50+ expectedStatus: 404,
51+ expectedBody: "404 page not found\n",
52+ }),
53+ Entry("with a request from the health check user agent", &requestTableInput{
54+ healthCheckPaths: []string{"/ping"},
55+ healthCheckUserAgents: []string{"hc/1.0"},
56+ requestString: "http://example.com/abc",
57+ headers: map[string]string{
58+ "User-Agent": "hc/1.0",
59+ },
60+ expectedStatus: 200,
61+ expectedBody: "OK",
62+ }),
63+ Entry("with a request from a different user agent", &requestTableInput{
64+ healthCheckPaths: []string{"/ping"},
65+ healthCheckUserAgents: []string{"hc/1.0"},
66+ requestString: "http://example.com/abc",
67+ headers: map[string]string{
68+ "User-Agent": "different",
69+ },
70+ expectedStatus: 404,
71+ expectedBody: "404 page not found\n",
72+ }),
73+ Entry("with multiple paths, request one of the healthcheck paths", &requestTableInput{
74+ healthCheckPaths: []string{"/ping", "/liveness_check", "/readiness_check"},
75+ healthCheckUserAgents: []string{"hc/1.0"},
76+ requestString: "http://example.com/readiness_check",
77+ headers: map[string]string{},
78+ expectedStatus: 200,
79+ expectedBody: "OK",
80+ }),
81+ Entry("with multiple paths, request none of the healthcheck paths", &requestTableInput{
82+ healthCheckPaths: []string{"/ping", "/liveness_check", "/readiness_check"},
83+ healthCheckUserAgents: []string{"hc/1.0"},
84+ requestString: "http://example.com/readiness",
85+ headers: map[string]string{
86+ "User-Agent": "user",
87+ },
88+ expectedStatus: 404,
89+ expectedBody: "404 page not found\n",
90+ }),
91+ Entry("with multiple user agents, request from a health check user agent", &requestTableInput{
92+ healthCheckPaths: []string{"/ping"},
93+ healthCheckUserAgents: []string{"hc/1.0", "GoogleHC/1.0"},
94+ requestString: "http://example.com/abc",
95+ headers: map[string]string{
96+ "User-Agent": "GoogleHC/1.0",
97+ },
98+ expectedStatus: 200,
99+ expectedBody: "OK",
100+ }),
101+ Entry("with multiple user agents, request from none of the health check user agents", &requestTableInput{
102+ healthCheckPaths: []string{"/ping"},
103+ healthCheckUserAgents: []string{"hc/1.0", "GoogleHC/1.0"},
104+ requestString: "http://example.com/abc",
105+ headers: map[string]string{
106+ "User-Agent": "user",
107+ },
108+ expectedStatus: 404,
109+ expectedBody: "404 page not found\n",
110+ }),
111+ )
112+})
pkg/middleware/middleware_suite_test.gonew file+16
@@ -0,0 +1,16 @@
1+package middleware
2+
3+import (
4+ "testing"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
7+ . "github.com/onsi/ginkgo"
8+ . "github.com/onsi/gomega"
9+)
10+
11+func TestMiddlewareSuite(t *testing.T) {
12+ logger.SetOutput(GinkgoWriter)
13+
14+ RegisterFailHandler(Fail)
15+ RunSpecs(t, "Middleware")
16+}
pkg/validation/logging.go+2−6
@@ -9,7 +9,7 @@ import (
99)
1010
1111// configureLogger is responsible for configuring the logger based on the options given
12−func configureLogger(o options.Logging, pingPath string, msgs []string) []string {
12+func configureLogger(o options.Logging, msgs []string) []string {
1313 // Setup the log file
1414 if len(o.File.Filename) > 0 {
1515 // Validate that the file/dir can be written
@@ -48,11 +48,7 @@ func configureLogger(o options.Logging, pingPath string, msgs []string) []string
4848 logger.SetAuthTemplate(o.AuthFormat)
4949 logger.SetReqTemplate(o.RequestFormat)
5050
51− excludePaths := o.ExcludePaths
52− if o.SilencePing {
53− excludePaths = append(excludePaths, pingPath)
54− }
55− logger.SetExcludePaths(excludePaths)
51+ logger.SetExcludePaths(o.ExcludePaths)
5652
5753 if !o.LocalTime {
5854 logger.SetFlags(logger.Flags() | logger.LUTC)
pkg/validation/options.go+1−1
@@ -264,7 +264,7 @@ func Validate(o *options.Options) error {
264264
265265 msgs = parseSignatureKey(o, msgs)
266266 msgs = validateCookieName(o, msgs)
267− msgs = configureLogger(o.Logging, o.PingPath, msgs)
267+ msgs = configureLogger(o.Logging, msgs)
268268
269269 if o.ReverseProxy {
270270 parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)