mirror of
https://github.com/rclone/rclone.git
synced 2024-11-08 01:25:14 +01:00
serve sftp: implement auth proxy
This commit is contained in:
parent
e6ab237fcd
commit
d75fbe4852
@ -47,7 +47,6 @@ func shellUnEscape(str string) string {
|
||||
// Info about the current connection
|
||||
type conn struct {
|
||||
vfs *vfs.VFS
|
||||
f fs.Fs
|
||||
handlers sftp.Handlers
|
||||
what string
|
||||
}
|
||||
@ -65,7 +64,7 @@ func (c *conn) execCommand(ctx context.Context, out io.Writer, command string) (
|
||||
fs.Debugf(c.what, "exec command: binary = %q, args = %q", binary, args)
|
||||
switch binary {
|
||||
case "df":
|
||||
about := c.f.Features().About
|
||||
about := c.vfs.Fs().Features().About
|
||||
if about == nil {
|
||||
return errors.New("df not supported")
|
||||
}
|
||||
@ -121,7 +120,7 @@ func (c *conn) execCommand(ctx context.Context, out io.Writer, command string) (
|
||||
// special cases for rclone command detection
|
||||
switch args {
|
||||
case "'abc' | md5sum":
|
||||
if c.f.Hashes().Contains(hash.MD5) {
|
||||
if c.vfs.Fs().Hashes().Contains(hash.MD5) {
|
||||
_, err = fmt.Fprintf(out, "0bee89b07a248e27c83fc3d5951213c1 -\n")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "send output failed")
|
||||
@ -130,7 +129,7 @@ func (c *conn) execCommand(ctx context.Context, out io.Writer, command string) (
|
||||
return errors.New("md5 hash not supported")
|
||||
}
|
||||
case "'abc' | sha1sum":
|
||||
if c.f.Hashes().Contains(hash.SHA1) {
|
||||
if c.vfs.Fs().Hashes().Contains(hash.SHA1) {
|
||||
_, err = fmt.Fprintf(out, "03cfd743661f07975fa2f1220c5194cbaff48451 -\n")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "send output failed")
|
||||
|
@ -19,14 +19,14 @@ type vfsHandler struct {
|
||||
}
|
||||
|
||||
// vfsHandler returns a Handlers object with the test handlers.
|
||||
func newVFSHandler(vfs *vfs.VFS) (sftp.Handlers, error) {
|
||||
func newVFSHandler(vfs *vfs.VFS) sftp.Handlers {
|
||||
v := vfsHandler{VFS: vfs}
|
||||
return sftp.Handlers{
|
||||
FileGet: v,
|
||||
FilePut: v,
|
||||
FileCmd: v,
|
||||
FileList: v,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/rclone/rclone/cmd/serve/proxy"
|
||||
"github.com/rclone/rclone/cmd/serve/proxy/proxyflags"
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/config"
|
||||
"github.com/rclone/rclone/lib/env"
|
||||
@ -33,21 +34,47 @@ type server struct {
|
||||
opt Options
|
||||
vfs *vfs.VFS
|
||||
config *ssh.ServerConfig
|
||||
handlers sftp.Handlers
|
||||
listener net.Listener
|
||||
waitChan chan struct{} // for waiting on the listener to close
|
||||
proxy *proxy.Proxy
|
||||
}
|
||||
|
||||
func newServer(f fs.Fs, opt *Options) *server {
|
||||
s := &server{
|
||||
f: f,
|
||||
vfs: vfs.New(f, &vfsflags.Opt),
|
||||
opt: *opt,
|
||||
waitChan: make(chan struct{}),
|
||||
}
|
||||
if proxyflags.Opt.AuthProxy != "" {
|
||||
s.proxy = proxy.New(&proxyflags.Opt)
|
||||
} else {
|
||||
s.vfs = vfs.New(f, &vfsflags.Opt)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// getVFS gets the vfs from s or the proxy
|
||||
func (s *server) getVFS(what string, sshConn *ssh.ServerConn) (VFS *vfs.VFS) {
|
||||
if s.proxy == nil {
|
||||
return s.vfs
|
||||
}
|
||||
if sshConn.Permissions == nil && sshConn.Permissions.Extensions == nil {
|
||||
fs.Infof(what, "SSH Permissions Extensions not found")
|
||||
return nil
|
||||
}
|
||||
key := sshConn.Permissions.Extensions["_vfsKey"]
|
||||
if key == "" {
|
||||
fs.Infof(what, "VFS key not found")
|
||||
return nil
|
||||
}
|
||||
VFS = s.proxy.Get(key)
|
||||
if VFS == nil {
|
||||
fs.Infof(what, "failed to read VFS from cache")
|
||||
return nil
|
||||
}
|
||||
return VFS
|
||||
}
|
||||
|
||||
func (s *server) acceptConnections() {
|
||||
for {
|
||||
nConn, err := s.listener.Accept()
|
||||
@ -73,11 +100,15 @@ func (s *server) acceptConnections() {
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
c := &conn{
|
||||
vfs: s.vfs,
|
||||
f: s.f,
|
||||
handlers: s.handlers,
|
||||
what: what,
|
||||
vfs: s.getVFS(what, sshConn),
|
||||
}
|
||||
if c.vfs == nil {
|
||||
fs.Infof(what, "Closing unauthenticated connection (couldn't find VFS)")
|
||||
_ = nConn.Close()
|
||||
continue
|
||||
}
|
||||
c.handlers = newVFSHandler(c.vfs)
|
||||
|
||||
// Accept all channels
|
||||
go c.handleChannels(chans)
|
||||
@ -109,7 +140,19 @@ func (s *server) serve() (err error) {
|
||||
ServerVersion: "SSH-2.0-" + fs.Config.UserAgent,
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
fs.Debugf(describeConn(c), "Password login attempt for %s", c.User())
|
||||
if s.opt.User != "" && s.opt.Pass != "" {
|
||||
if s.proxy != nil {
|
||||
// query the proxy for the config
|
||||
_, vfsKey, err := s.proxy.Call(c.User(), string(pass))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// just return the Key so we can get it back from the cache
|
||||
return &ssh.Permissions{
|
||||
Extensions: map[string]string{
|
||||
"_vfsKey": vfsKey,
|
||||
},
|
||||
}, nil
|
||||
} else if s.opt.User != "" && s.opt.Pass != "" {
|
||||
userOK := subtle.ConstantTimeCompare([]byte(c.User()), []byte(s.opt.User))
|
||||
passOK := subtle.ConstantTimeCompare(pass, []byte(s.opt.Pass))
|
||||
if (userOK & passOK) == 1 {
|
||||
@ -120,6 +163,9 @@ func (s *server) serve() (err error) {
|
||||
},
|
||||
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
fs.Debugf(describeConn(c), "Public key login attempt for %s", c.User())
|
||||
if s.proxy != nil {
|
||||
return nil, errors.New("public key login not allowed when using auth proxy")
|
||||
}
|
||||
if _, ok := authorizedKeysMap[string(pubKey.Marshal())]; ok {
|
||||
return &ssh.Permissions{
|
||||
// Record the public key used for authentication.
|
||||
@ -178,11 +224,6 @@ func (s *server) serve() (err error) {
|
||||
}
|
||||
fs.Logf(nil, "SFTP server listening on %v\n", s.listener.Addr())
|
||||
|
||||
s.handlers, err = newVFSHandler(s.vfs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "serve sftp: failed to create fs")
|
||||
}
|
||||
|
||||
go s.acceptConnections()
|
||||
|
||||
return nil
|
||||
|
@ -6,6 +6,9 @@ package sftp
|
||||
|
||||
import (
|
||||
"github.com/rclone/rclone/cmd"
|
||||
"github.com/rclone/rclone/cmd/serve/proxy"
|
||||
"github.com/rclone/rclone/cmd/serve/proxy/proxyflags"
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/config/flags"
|
||||
"github.com/rclone/rclone/fs/rc"
|
||||
"github.com/rclone/rclone/vfs"
|
||||
@ -46,6 +49,7 @@ func AddFlags(flagSet *pflag.FlagSet, Opt *Options) {
|
||||
|
||||
func init() {
|
||||
vfsflags.AddFlags(Command.Flags())
|
||||
proxyflags.AddFlags(Command.Flags())
|
||||
AddFlags(Command.Flags(), &Opt)
|
||||
}
|
||||
|
||||
@ -84,10 +88,15 @@ reachable externally then supply "--addr :2022" for example.
|
||||
Note that the default of "--vfs-cache-mode off" is fine for the rclone
|
||||
sftp backend, but it may not be with other SFTP clients.
|
||||
|
||||
` + vfs.Help,
|
||||
` + vfs.Help + proxy.Help,
|
||||
Run: func(command *cobra.Command, args []string) {
|
||||
var f fs.Fs
|
||||
if proxyflags.Opt.AuthProxy == "" {
|
||||
cmd.CheckArgs(1, 1, command, args)
|
||||
f := cmd.NewFsSrc(args)
|
||||
f = cmd.NewFsSrc(args)
|
||||
} else {
|
||||
cmd.CheckArgs(0, 0, command, args)
|
||||
}
|
||||
cmd.Run(false, true, command, func() error {
|
||||
s := newServer(f, &Opt)
|
||||
err := s.Serve()
|
||||
|
Loading…
Reference in New Issue
Block a user