diff --git a/web/handler.go b/web/handler.go index ae3ebc03..54f1e61c 100644 --- a/web/handler.go +++ b/web/handler.go @@ -81,10 +81,18 @@ type webHandler struct { cache *cache // bcryptMtx is there to ensure that bcrypt.CompareHashAndPassword is run // only once in parallel as this is CPU intensive. - bcryptMtx sync.Mutex + bcryptMtx sync.Mutex + authExcludedPaths map[string]struct{} } func (u *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Check if path has auth excluded. + if _, ok := u.authExcludedPaths[r.URL.Path]; ok { + u.logger.Log("msg", "Bypassing auth, path in auth_excluded_paths", "path", r.URL.Path) + u.handler.ServeHTTP(w, r) + return + } + c, err := getConfig(u.tlsConfigPath) if err != nil { u.logger.Log("msg", "Unable to parse configuration", "err", err) diff --git a/web/handler_test.go b/web/handler_test.go index 9bd6a506..a921691e 100644 --- a/web/handler_test.go +++ b/web/handler_test.go @@ -129,6 +129,48 @@ func TestBasicAuthWithFakepassword(t *testing.T) { login() } +// TestAuthExcludedPath validates that we auth is bypassed for the paths in +// auth_excluded_paths. +func TestAuthExcludedPath(t *testing.T) { + server := &http.Server{ + Addr: port, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World!")) + }), + } + + done := make(chan struct{}) + t.Cleanup(func() { + if err := server.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + <-done + }) + + go func() { + ListenAndServe(server, "testdata/web_config_auth_excluded_paths.good.yml", testlogger) + close(done) + }() + + makeRequest := func(path string, statusCode int) { + client := &http.Client{} + req, err := http.NewRequest("GET", "http://localhost"+port+path, nil) + if err != nil { + t.Fatal(err) + } + r, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + if r.StatusCode != statusCode { + t.Fatalf("bad return code, expected %d, got %d", statusCode, r.StatusCode) + } + } + + makeRequest("/not-metrics", 401) + makeRequest("/metrics", 200) +} + // TestHTTPHeaders validates that HTTP headers are added correctly. func TestHTTPHeaders(t *testing.T) { server := &http.Server{ diff --git a/web/testdata/web_config_auth_excluded_paths.good.yml b/web/testdata/web_config_auth_excluded_paths.good.yml new file mode 100644 index 00000000..421b4b29 --- /dev/null +++ b/web/testdata/web_config_auth_excluded_paths.good.yml @@ -0,0 +1,5 @@ +auth_excluded_paths: + - /metrics + +basic_auth_users: + admin: $2b$12$hNf2lSsxfm0.i4a.1kVpSOVyBCfIB51VRjgBUyv6kdnyTlgWj81Ay diff --git a/web/tls_config.go b/web/tls_config.go index 2668964a..5dd78cf4 100644 --- a/web/tls_config.go +++ b/web/tls_config.go @@ -34,9 +34,10 @@ var ( ) type Config struct { - TLSConfig TLSStruct `yaml:"tls_server_config"` - HTTPConfig HTTPStruct `yaml:"http_server_config"` - Users map[string]config_util.Secret `yaml:"basic_auth_users"` + TLSConfig TLSStruct `yaml:"tls_server_config"` + HTTPConfig HTTPStruct `yaml:"http_server_config"` + Users map[string]config_util.Secret `yaml:"basic_auth_users"` + AuthExcludedPaths []string `yaml:"auth_excluded_paths"` } type TLSStruct struct { @@ -211,11 +212,17 @@ func Serve(l net.Listener, server *http.Server, tlsConfigPath string, logger log return err } + authExcludedPaths := make(map[string]struct{}) + for _, path := range c.AuthExcludedPaths { + authExcludedPaths[path] = struct{}{} + } + server.Handler = &webHandler{ - tlsConfigPath: tlsConfigPath, - logger: logger, - handler: handler, - cache: newCache(), + tlsConfigPath: tlsConfigPath, + logger: logger, + handler: handler, + cache: newCache(), + authExcludedPaths: authExcludedPaths, } config, err := ConfigToTLSConfig(&c.TLSConfig)