mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-01-22 05:19:26 +02:00
Allow a health/ping request to be identified by User-Agent (#567)
* Add an option to allow health checks based on User-Agent. * Formatting fix * Rename field and avoid unnecessary interface. * Skip the redirect fix so it can be put into a different PR. * Add CHANGELOG entry * Adding a couple tests for the PingUserAgent option.
This commit is contained in:
parent
160bbaf98e
commit
2c851fcd4f
@ -58,6 +58,7 @@
|
||||
- [#560](https://github.com/oauth2-proxy/oauth2-proxy/pull/560) Fallback to UserInfo is User ID claim not present (@JoelSpeed)
|
||||
- [#598](https://github.com/oauth2-proxy/oauth2-proxy/pull/598) acr_values no longer sent to IdP when empty (@ScottGuymer)
|
||||
- [#548](https://github.com/oauth2-proxy/oauth2-proxy/pull/548) Separate logging options out of main options structure (@JoelSpeed)
|
||||
- [#567](https://github.com/oauth2-proxy/oauth2-proxy/pull/567) Allow health/ping request to be identified via User-Agent (@chkohner)
|
||||
- [#536](https://github.com/oauth2-proxy/oauth2-proxy/pull/536) Improvements to Session State code (@JoelSpeed)
|
||||
- [#573](https://github.com/oauth2-proxy/oauth2-proxy/pull/573) Properly parse redis urls for cluster and sentinel connections (@amnay-mo)
|
||||
- [#574](https://github.com/oauth2-proxy/oauth2-proxy/pull/574) render error page on 502 proxy status (@amnay-mo)
|
||||
|
@ -88,6 +88,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
|
||||
| `--provider` | string | OAuth provider | google |
|
||||
| `--provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
|
||||
| `--ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
|
||||
| `--ping-user-agent` | string | a User-Agent that can be used for basic health checks | `""` (don't check user agent) |
|
||||
| `--proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` |
|
||||
| `--proxy-websockets` | bool | enables WebSocket proxying | true |
|
||||
| `--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
|
||||
|
||||
Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
### Auth Log Format
|
||||
Authentication 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:
|
||||
|
@ -21,6 +21,7 @@ type responseLogger struct {
|
||||
size int
|
||||
upstream string
|
||||
authInfo string
|
||||
silent bool
|
||||
}
|
||||
|
||||
// Header returns the ResponseWriter's Header
|
||||
@ -104,5 +105,7 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
url := *req.URL
|
||||
responseLogger := &responseLogger{w: w}
|
||||
h.handler.ServeHTTP(responseLogger, req)
|
||||
logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
|
||||
if !responseLogger.silent {
|
||||
logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, responseLogger.Status(), responseLogger.Size())
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,14 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
|
||||
)
|
||||
|
||||
func TestLoggingHandler_ServeHTTP(t *testing.T) {
|
||||
@ -67,3 +70,59 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggingHandler_PingUserAgent(t *testing.T) {
|
||||
tests := []struct {
|
||||
ExpectedLogMessage string
|
||||
Path string
|
||||
SilencePingLogging bool
|
||||
WithUserAgent string
|
||||
}{
|
||||
{"444\n", "/foo", true, "Blah"},
|
||||
{"444\n", "/foo", false, "Blah"},
|
||||
{"", "/ping", true, "Blah"},
|
||||
{"200\n", "/ping", false, "Blah"},
|
||||
{"", "/ping", true, "PingMe!"},
|
||||
{"", "/ping", false, "PingMe!"},
|
||||
{"", "/foo", true, "PingMe!"},
|
||||
{"", "/foo", false, "PingMe!"},
|
||||
}
|
||||
|
||||
for idx, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts.PingUserAgent = "PingMe!"
|
||||
opts.SkipAuthRegex = []string{"/foo"}
|
||||
opts.Upstreams = []string{"static://444/foo"}
|
||||
opts.Logging.SilencePing = test.SilencePingLogging
|
||||
if test.SilencePingLogging {
|
||||
opts.Logging.ExcludePaths = []string{"/ping"}
|
||||
}
|
||||
opts.RawRedirectURL = "localhost"
|
||||
validation.Validate(opts)
|
||||
|
||||
p := NewOAuthProxy(opts, func(email string) bool {
|
||||
return true
|
||||
})
|
||||
p.provider = NewTestProvider(&url.URL{Host: "localhost"}, "")
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
logger.SetOutput(buf)
|
||||
logger.SetReqEnabled(true)
|
||||
logger.SetReqTemplate("{{.StatusCode}}")
|
||||
|
||||
r, _ := http.NewRequest("GET", test.Path, nil)
|
||||
if test.WithUserAgent != "" {
|
||||
r.Header.Set("User-Agent", test.WithUserAgent)
|
||||
}
|
||||
|
||||
h := LoggingHandler(p)
|
||||
h.ServeHTTP(httptest.NewRecorder(), r)
|
||||
|
||||
actual := buf.String()
|
||||
if !strings.Contains(actual, test.ExpectedLogMessage) {
|
||||
t.Errorf("Log message was\n%s\ninstead of matching \n%s", actual, test.ExpectedLogMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +82,8 @@ type OAuthProxy struct {
|
||||
|
||||
RobotsPath string
|
||||
PingPath string
|
||||
PingUserAgent string
|
||||
SilencePings bool
|
||||
SignInPath string
|
||||
SignOutPath string
|
||||
OAuthStartPath string
|
||||
@ -312,6 +314,8 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) *OAuthPro
|
||||
|
||||
RobotsPath: "/robots.txt",
|
||||
PingPath: opts.PingPath,
|
||||
PingUserAgent: opts.PingUserAgent,
|
||||
SilencePings: opts.Logging.SilencePing,
|
||||
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
|
||||
SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
|
||||
OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
|
||||
@ -466,6 +470,11 @@ func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
|
||||
|
||||
// PingPage responds 200 OK to requests
|
||||
func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
|
||||
if p.SilencePings {
|
||||
if rl, ok := rw.(*responseLogger); ok {
|
||||
rl.silent = true
|
||||
}
|
||||
}
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(rw, "OK")
|
||||
}
|
||||
@ -675,6 +684,17 @@ func prepareNoCache(w http.ResponseWriter) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsPingRequest will check if the request appears to be performing a health check
|
||||
// either via the path it's requesting or by a special User-Agent configuration.
|
||||
func (p *OAuthProxy) IsPingRequest(req *http.Request) bool {
|
||||
|
||||
if req.URL.EscapedPath() == p.PingPath {
|
||||
return true
|
||||
}
|
||||
|
||||
return p.PingUserAgent != "" && req.Header.Get("User-Agent") == p.PingUserAgent
|
||||
}
|
||||
|
||||
func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.URL.Path, p.ProxyPrefix) {
|
||||
prepareNoCache(rw)
|
||||
@ -683,7 +703,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
switch path := req.URL.Path; {
|
||||
case path == p.RobotsPath:
|
||||
p.RobotsTxt(rw)
|
||||
case path == p.PingPath:
|
||||
case p.IsPingRequest(req):
|
||||
p.PingPage(rw)
|
||||
case p.IsWhitelistedRequest(req):
|
||||
p.serveMux.ServeHTTP(rw, req)
|
||||
|
@ -24,6 +24,7 @@ type SignatureData struct {
|
||||
type Options struct {
|
||||
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
|
||||
PingPath string `flag:"ping-path" cfg:"ping_path"`
|
||||
PingUserAgent string `flag:"ping-user-agent" cfg:"ping_user_agent"`
|
||||
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
|
||||
HTTPAddress string `flag:"http-address" cfg:"http_address"`
|
||||
HTTPSAddress string `flag:"https-address" cfg:"https_address"`
|
||||
@ -245,6 +246,7 @@ func NewFlagSet() *pflag.FlagSet {
|
||||
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
||||
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
|
||||
flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks")
|
||||
flagSet.String("ping-user-agent", "", "special User-Agent that will be used for basic health checks")
|
||||
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
|
||||
|
||||
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
|
||||
|
Loading…
x
Reference in New Issue
Block a user