git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge branch 'master' into helm-example
commita73d0ec268cbaa3f8c10d36ec35bd07639c73c5c authorEvgeni Gordeev <evgeni.gordeev@gmail.com> dateFri 12 Jun 2020, 13:14 (6 years ago) parent0f343cf1683aa5
8 files changed+108−20
CHANGELOG.md+2 docs/configuration/configuration.md+2−1 logging_handler.go+4−1 logging_handler_test.go+59 oauthproxy.go+21−1 pkg/apis/options/options.go+2 pkg/apis/sessions/session_state.go+8 pkg/apis/sessions/session_state_test.go+10−17
CHANGELOG.md+2
@@ -55,9 +55,11 @@
5555
5656## Changes since v5.1.1
5757
58+- [#601](https://github.com/oauth2-proxy/oauth2-proxy/pull/601) Ensure decrypted user/email are valid UTF8 (@JoelSpeed)
5859- [#560](https://github.com/oauth2-proxy/oauth2-proxy/pull/560) Fallback to UserInfo is User ID claim not present (@JoelSpeed)
5960- [#598](https://github.com/oauth2-proxy/oauth2-proxy/pull/598) acr_values no longer sent to IdP when empty (@ScottGuymer)
6061- [#548](https://github.com/oauth2-proxy/oauth2-proxy/pull/548) Separate logging options out of main options structure (@JoelSpeed)
62+- [#567](https://github.com/oauth2-proxy/oauth2-proxy/pull/567) Allow health/ping request to be identified via User-Agent (@chkohner)
6163- [#536](https://github.com/oauth2-proxy/oauth2-proxy/pull/536) Improvements to Session State code (@JoelSpeed)
6264- [#573](https://github.com/oauth2-proxy/oauth2-proxy/pull/573) Properly parse redis urls for cluster and sentinel connections (@amnay-mo)
6365- [#574](https://github.com/oauth2-proxy/oauth2-proxy/pull/574) render error page on 502 proxy status (@amnay-mo)
docs/configuration/configuration.md+2−1
@@ -88,6 +88,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
8888| `--provider` | string | OAuth provider | google |
8989| `--provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
9090| `--ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
91+| `--ping-user-agent` | string | a User-Agent that can be used for basic health checks | `""` (don't check user agent) |
9192| `--proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` |
9293| `--proxy-websockets` | bool | enables WebSocket proxying | true |
9394| `--pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | |
@@ -163,7 +164,7 @@ There are three different types of logging: standard, authentication, and HTTP r
163164
164165Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log.
165166
166−Logging of requests to the `/ping` endpoint can be disabled with `--silence-ping-logging` reducing log volume. This flag appends the `--ping-path` to `--exclude-logging-paths`.
167+Logging of requests to the `/ping` endpoint (or using `--ping-user-agent`) can be disabled with `--silence-ping-logging` reducing log volume. This flag appends the `--ping-path` to `--exclude-logging-paths`.
167168
168169### Auth Log Format
169170Authentication logs are logs which are guaranteed to contain a username or email address of a user attempting to authenticate. These logs are output by default in the below format:
logging_handler.go+4−1
@@ -21,6 +21,7 @@ type responseLogger struct {
2121 size int
2222 upstream string
2323 authInfo string
24+ silent bool
2425}
2526
2627// Header returns the ResponseWriter's Header
@@ -104,5 +105,7 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
104105 url := *req.URL
105106 responseLogger := &responseLogger{w: w}
106107 h.handler.ServeHTTP(responseLogger, req)
107− logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
108+ if !responseLogger.silent {
109+ logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
110+ }
108111}
logging_handler_test.go+59
@@ -5,11 +5,14 @@ import (
55 "fmt"
66 "net/http"
77 "net/http/httptest"
8+ "net/url"
89 "strings"
910 "testing"
1011 "time"
1112
13+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
1214 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
15+ "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
1316)
1417
1518func TestLoggingHandler_ServeHTTP(t *testing.T) {
@@ -67,3 +70,59 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
6770 }
6871 }
6972}
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+}
oauthproxy.go+21−1
@@ -82,6 +82,8 @@ type OAuthProxy struct {
8282
8383 RobotsPath string
8484 PingPath string
85+ PingUserAgent string
86+ SilencePings bool
8587 SignInPath string
8688 SignOutPath string
8789 OAuthStartPath string
@@ -312,6 +314,8 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) *OAuthPro
312314
313315 RobotsPath: "/robots.txt",
314316 PingPath: opts.PingPath,
317+ PingUserAgent: opts.PingUserAgent,
318+ SilencePings: opts.Logging.SilencePing,
315319 SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
316320 SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
317321 OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
@@ -466,6 +470,11 @@ func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
466470
467471// PingPage responds 200 OK to requests
468472func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
473+ if p.SilencePings {
474+ if rl, ok := rw.(*responseLogger); ok {
475+ rl.silent = true
476+ }
477+ }
469478 rw.WriteHeader(http.StatusOK)
470479 fmt.Fprintf(rw, "OK")
471480}
@@ -675,6 +684,17 @@ func prepareNoCache(w http.ResponseWriter) {
675684 }
676685}
677686
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+
678698func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
679699 if strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
680700 prepareNoCache(rw)
@@ -683,7 +703,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
683703 switch path := req.URL.Path; {
684704 case path == p.RobotsPath:
685705 p.RobotsTxt(rw)
686− case path == p.PingPath:
706+ case p.IsPingRequest(req):
687707 p.PingPage(rw)
688708 case p.IsWhitelistedRequest(req):
689709 p.serveMux.ServeHTTP(rw, req)
pkg/apis/options/options.go+2
@@ -24,6 +24,7 @@ type SignatureData struct {
2424type Options struct {
2525 ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
2626 PingPath string `flag:"ping-path" cfg:"ping_path"`
27+ PingUserAgent string `flag:"ping-user-agent" cfg:"ping_user_agent"`
2728 ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
2829 HTTPAddress string `flag:"http-address" cfg:"http_address"`
2930 HTTPSAddress string `flag:"https-address" cfg:"https_address"`
@@ -245,6 +246,7 @@ func NewFlagSet() *pflag.FlagSet {
245246 flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
246247 flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
247248 flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks")
249+ flagSet.String("ping-user-agent", "", "special User-Agent that will be used for basic health checks")
248250 flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
249251
250252 flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
pkg/apis/sessions/session_state.go+8
@@ -2,8 +2,10 @@ package sessions
22
33import (
44 "encoding/json"
5+ "errors"
56 "fmt"
67 "time"
8+ "unicode/utf8"
79
810 "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
911)
@@ -106,6 +108,9 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
106108 if ss.Email != "" {
107109 decryptedEmail, errEmail := c.Decrypt(ss.Email)
108110 if errEmail == nil {
111+ if !utf8.ValidString(decryptedEmail) {
112+ return nil, errors.New("invalid value for decrypted email")
113+ }
109114 ss.Email = decryptedEmail
110115 }
111116 }
@@ -113,6 +118,9 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
113118 if ss.User != "" {
114119 decryptedUser, errUser := c.Decrypt(ss.User)
115120 if errUser == nil {
121+ if !utf8.ValidString(decryptedUser) {
122+ return nil, errors.New("invalid value for decrypted user")
123+ }
116124 ss.User = decryptedUser
117125 }
118126 }
pkg/apis/sessions/session_state_test.go+10−17
@@ -49,15 +49,7 @@ func TestSessionStateSerialization(t *testing.T) {
4949 // ensure a different cipher can't decode properly (ie: it gets gibberish)
5050 ss, err = sessions.DecodeSessionState(encoded, c2)
5151 t.Logf("%#v", ss)
52− assert.Equal(t, nil, err)
53− assert.NotEqual(t, "user@domain.com", ss.User)
54− assert.NotEqual(t, s.Email, ss.Email)
55− assert.NotEqual(t, s.PreferredUsername, ss.PreferredUsername)
56− assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
57− assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
58− assert.NotEqual(t, s.AccessToken, ss.AccessToken)
59− assert.NotEqual(t, s.IDToken, ss.IDToken)
60− assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
52+ assert.NotEqual(t, nil, err)
6153}
6254
6355func TestSessionStateSerializationWithUser(t *testing.T) {
@@ -91,14 +83,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
9183 // ensure a different cipher can't decode properly (ie: it gets gibberish)
9284 ss, err = sessions.DecodeSessionState(encoded, c2)
9385 t.Logf("%#v", ss)
94− assert.Equal(t, nil, err)
95− assert.NotEqual(t, s.User, ss.User)
96− assert.NotEqual(t, s.Email, ss.Email)
97− assert.NotEqual(t, s.PreferredUsername, ss.PreferredUsername)
98− assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
99− assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
100− assert.NotEqual(t, s.AccessToken, ss.AccessToken)
101− assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
86+ assert.NotEqual(t, nil, err)
10287}
10388
10489func TestSessionStateSerializationNoCipher(t *testing.T) {
@@ -278,6 +263,14 @@ func TestDecodeSessionState(t *testing.T) {
278263 Cipher: c,
279264 Error: true,
280265 },
266+ {
267+ SessionState: sessions.SessionState{
268+ Email: "user@domain.com",
269+ User: "YmFzZTY0LWVuY29kZWQtdXNlcgo=", // Base64 encoding of base64-encoded-user
270+ },
271+ Error: true,
272+ Cipher: c,
273+ },
281274 }
282275
283276 for i, tc := range testCases {