From fa0a9653d24c8f7cc1d2d3a3b752e26b1b2f6179 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sat, 3 Nov 2018 16:37:09 +0000 Subject: [PATCH] rc: methods marked as AuthRequired need auth unless --rc-no-auth Methods which can read or mutate external storage will require authorisation - enforce this. This can be overidden by `--rc-no-auth`. --- cmd/serve/httplib/httplib.go | 7 ++++ docs/content/rc.md | 13 ++++++++ fs/rc/internal.go | 10 ++++++ fs/rc/rc.go | 1 + fs/rc/rcflags/rcflags.go | 1 + fs/rc/rcserver/rcserver.go | 6 ++++ fs/rc/rcserver/rcserver_test.go | 59 +++++++++++++++++++++++++++++++++ fs/rc/registry.go | 9 ++--- 8 files changed, 102 insertions(+), 4 deletions(-) diff --git a/cmd/serve/httplib/httplib.go b/cmd/serve/httplib/httplib.go index fa5c3ea37..f39a1d901 100644 --- a/cmd/serve/httplib/httplib.go +++ b/cmd/serve/httplib/httplib.go @@ -106,6 +106,7 @@ type Server struct { httpServer *http.Server basicPassHashed string useSSL bool // if server is configured for SSL/TLS + usingAuth bool // set if authentication is configured } // singleUserProvider provides the encrypted password for a single user @@ -143,6 +144,7 @@ func NewServer(handler http.Handler, opt *Options) *Server { } authenticator := auth.NewBasicAuthenticator(s.Opt.Realm, secretProvider) handler = auth.JustCheck(authenticator, handler.ServeHTTP) + s.usingAuth = true } s.useSSL = s.Opt.SslKey != "" @@ -255,3 +257,8 @@ func (s *Server) URL() string { } return fmt.Sprintf("%s://%s/", proto, addr) } + +// UsingAuth returns true if authentication is required +func (s *Server) UsingAuth() bool { + return s.usingAuth +} diff --git a/docs/content/rc.md b/docs/content/rc.md index e4d40b38c..c2a4ca7e5 100644 --- a/docs/content/rc.md +++ b/docs/content/rc.md @@ -81,6 +81,19 @@ implementing browser based GUIs for rclone functions. Default Off. +### --rc-no-auth + +By default rclone will require authorisation to have been set up on +the rc interface in order to use any methods which access any rclone +remotes. Eg `operations/list` is denied as it involved creating a +remote as is `sync/copy`. + +If this is set then no authorisation will be required on the server to +use these methods. The alternative is to use `--rc-user` and +`--rc-pass` and use these credentials in the request. + +Default Off. + ## Accessing the remote control via the rclone rc command Rclone itself implements the remote control protocol in its `rclone diff --git a/fs/rc/internal.go b/fs/rc/internal.go index 43fb19bc7..b5e8a1d00 100644 --- a/fs/rc/internal.go +++ b/fs/rc/internal.go @@ -17,6 +17,16 @@ func init() { Help: ` This echoes the input parameters to the output parameters for testing purposes. It can be used to check that rclone is still alive and to +check that parameter passing is working properly.`, + }) + Add(Call{ + Path: "rc/noopauth", + AuthRequired: true, + Fn: rcNoop, + Title: "Echo the input to the output parameters requiring auth", + Help: ` +This echoes the input parameters to the output parameters for testing +purposes. It can be used to check that rclone is still alive and to check that parameter passing is working properly.`, }) Add(Call{ diff --git a/fs/rc/rc.go b/fs/rc/rc.go index 1f7dc5f0e..2d9e5590b 100644 --- a/fs/rc/rc.go +++ b/fs/rc/rc.go @@ -21,6 +21,7 @@ type Options struct { Enabled bool // set to enable the server Serve bool // set to serve files from remotes Files string // set to enable serving files locally + NoAuth bool // set to disable auth checks on AuthRequired methods } // DefaultOpt is the default values used for Options diff --git a/fs/rc/rcflags/rcflags.go b/fs/rc/rcflags/rcflags.go index 0efc6c946..10f369272 100644 --- a/fs/rc/rcflags/rcflags.go +++ b/fs/rc/rcflags/rcflags.go @@ -19,5 +19,6 @@ func AddFlags(flagSet *pflag.FlagSet) { flags.BoolVarP(flagSet, &Opt.Enabled, "rc", "", false, "Enable the remote control server.") flags.StringVarP(flagSet, &Opt.Files, "rc-files", "", "", "Path to local files to serve on the HTTP server.") flags.BoolVarP(flagSet, &Opt.Serve, "rc-serve", "", false, "Enable the serving of remote objects.") + flags.BoolVarP(flagSet, &Opt.NoAuth, "rc-no-auth", "", false, "Don't require auth for certain methods.") httpflags.AddFlagsPrefix(flagSet, "rc-", &Opt.HTTPOptions) } diff --git a/fs/rc/rcserver/rcserver.go b/fs/rc/rcserver/rcserver.go index 2dc3cda9c..e8d187031 100644 --- a/fs/rc/rcserver/rcserver.go +++ b/fs/rc/rcserver/rcserver.go @@ -159,6 +159,12 @@ func (s *Server) handlePost(w http.ResponseWriter, r *http.Request, path string) return } + // Check to see if it requires authorisation + if !s.opt.NoAuth && call.AuthRequired && !s.UsingAuth() { + writeError(path, in, w, errors.Errorf("authentication must be set up on the rc server to use %q or the --rc-no-auth flag must be in use", path), http.StatusForbidden) + return + } + // Check to see if it is async or not isAsync, err := in.GetBool("_async") if rc.NotErrParamNotFound(err) { diff --git a/fs/rc/rcserver/rcserver_test.go b/fs/rc/rcserver/rcserver_test.go index 816c014af..6bd7d698f 100644 --- a/fs/rc/rcserver/rcserver_test.go +++ b/fs/rc/rcserver/rcserver_test.go @@ -537,6 +537,65 @@ func TestNoServe(t *testing.T) { testServer(t, tests, &opt) } +func TestAuthRequired(t *testing.T) { + tests := []testRun{{ + Name: "auth", + URL: "rc/noopauth", + Method: "POST", + Body: `{}`, + ContentType: "application/javascript", + Status: http.StatusForbidden, + Expected: `{ + "error": "authentication must be set up on the rc server to use \"rc/noopauth\" or the --rc-no-auth flag must be in use", + "input": {}, + "path": "rc/noopauth", + "status": 403 +} +`, + }} + opt := newTestOpt() + opt.Serve = false + opt.Files = "" + opt.NoAuth = false + testServer(t, tests, &opt) +} + +func TestNoAuth(t *testing.T) { + tests := []testRun{{ + Name: "auth", + URL: "rc/noopauth", + Method: "POST", + Body: `{}`, + ContentType: "application/javascript", + Status: http.StatusOK, + Expected: "{}\n", + }} + opt := newTestOpt() + opt.Serve = false + opt.Files = "" + opt.NoAuth = true + testServer(t, tests, &opt) +} + +func TestWithUserPass(t *testing.T) { + tests := []testRun{{ + Name: "auth", + URL: "rc/noopauth", + Method: "POST", + Body: `{}`, + ContentType: "application/javascript", + Status: http.StatusOK, + Expected: "{}\n", + }} + opt := newTestOpt() + opt.Serve = false + opt.Files = "" + opt.NoAuth = false + opt.HTTPOptions.BasicUser = "user" + opt.HTTPOptions.BasicPass = "pass" + testServer(t, tests, &opt) +} + func TestRCAsync(t *testing.T) { tests := []testRun{{ Name: "ok", diff --git a/fs/rc/registry.go b/fs/rc/registry.go index 6926f14be..709540c59 100644 --- a/fs/rc/registry.go +++ b/fs/rc/registry.go @@ -16,10 +16,11 @@ type Func func(in Params) (out Params, err error) // Call defines info about a remote control function and is used in // the Add function to create new entry points. type Call struct { - Path string // path to activate this RC - Fn Func `json:"-"` // function to call - Title string // help for the function - Help string // multi-line markdown formatted help + Path string // path to activate this RC + Fn Func `json:"-"` // function to call + Title string // help for the function + AuthRequired bool // if set then this call requires authorisation to be set + Help string // multi-line markdown formatted help } // Registry holds the list of all the registered remote control functions