diff --git a/docs/content/rc.md b/docs/content/rc.md index 49cb5e58f..ea3d0a00b 100644 --- a/docs/content/rc.md +++ b/docs/content/rc.md @@ -2742,6 +2742,14 @@ curl -H "Content-Type: application/json" -X POST -d '{"potato":2,"sausage":1}' ' If you use the `--rc` flag this will also enable the use of the go profiling tools on the same port. +The profiling endpoints follow the same authentication rules as the +rest of the rc: they are only served if authentication has been set up +(`--rc-user`/`--rc-pass` or `--rc-htpasswd`) or the +[`--rc-no-auth`](#--rc-no-auth) flag is in use. For debugging on the +default localhost port the easiest thing is to use `--rc --rc-no-auth` +(but see the warning about using `--rc-no-auth` on a non-loopback +bind). + To use these, first [install go](https://golang.org/doc/install). ### Debugging memory use diff --git a/fs/rc/rcserver/rcserver.go b/fs/rc/rcserver/rcserver.go index 37b3c587f..c32aa0ea6 100644 --- a/fs/rc/rcserver/rcserver.go +++ b/fs/rc/rcserver/rcserver.go @@ -126,8 +126,11 @@ func newServer(ctx context.Context, opt *rc.Options, mux *http.ServeMux) (*Serve middleware.SetHeader("Server", "rclone/"+fs.Version), ) - // Add the debug handler which is installed in the default mux - router.Handle("/debug/pprof/*", mux) + // Add the debug handler which is installed in the default mux. + // Only do this if auth is enabled. + if s.noAuth || s.server.UsingAuth() { + router.Handle("/debug/pprof/*", mux) + } // FIXME split these up into individual functions router.Get("/*", s.handler) diff --git a/fs/rc/rcserver/rcserver_test.go b/fs/rc/rcserver/rcserver_test.go index 98bc895c6..11cacf964 100644 --- a/fs/rc/rcserver/rcserver_test.go +++ b/fs/rc/rcserver/rcserver_test.go @@ -977,27 +977,62 @@ func TestRCAsync(t *testing.T) { testServer(t, tests, &opt) } -// Check the debug handlers are attached +// The debug/pprof handlers expose the process command line, heap and +// goroutine dumps, so require auth. func TestRCDebug(t *testing.T) { - tests := []testRun{{ + index := testRun{ Name: "index", URL: "debug/pprof/", Method: "GET", ContentType: "text/html", Status: http.StatusOK, Contains: regexp.MustCompile(`Types of profiles available`), - }, { + } + goroutines := testRun{ Name: "goroutines", URL: "debug/pprof/goroutine?debug=1", Method: "GET", ContentType: "text/html", Status: http.StatusOK, Contains: regexp.MustCompile(`goroutine profile`), - }} - opt := newTestOpt() - opt.Serve = true - opt.Files = "" - testServer(t, tests, &opt) + } + + // With --rc-no-auth the debug handlers are attached and open + t.Run("NoAuth", func(t *testing.T) { + opt := newTestOpt() + opt.Serve = true + opt.Files = "" + opt.NoAuth = true + testServer(t, []testRun{index, goroutines}, &opt) + }) + + // With auth configured the debug handlers are attached but require credentials + t.Run("WithAuth", func(t *testing.T) { + authed := index + authed.User, authed.Pass = "user", "pass" + unauthed := index + unauthed.Name = "index-no-credentials" + unauthed.Status = http.StatusUnauthorized + unauthed.Contains = regexp.MustCompile(`Unauthorized`) + opt := newTestOpt() + opt.Serve = true + opt.Files = "" + opt.Auth.BasicUser = "user" + opt.Auth.BasicPass = "pass" + testServer(t, []testRun{authed, unauthed}, &opt) + }) + + // On a default unauthenticated server the debug handlers must not be + // mounted, so they bypass nothing - they simply aren't there + t.Run("FailClosed", func(t *testing.T) { + blocked := index + blocked.Status = http.StatusNotFound + blocked.Contains = regexp.MustCompile(`Not Found`) + opt := newTestOpt() + opt.Serve = true + opt.Files = "" + testServer(t, []testRun{blocked}, &opt) + }) } func TestServeModTime(t *testing.T) {