serve sftp: implement auth proxy

This commit is contained in:
Nick Craig-Wood 2019-07-31 22:20:46 +01:00
parent e6ab237fcd
commit d75fbe4852
4 changed files with 71 additions and 22 deletions

View File

@ -47,7 +47,6 @@ func shellUnEscape(str string) string {
// Info about the current connection // Info about the current connection
type conn struct { type conn struct {
vfs *vfs.VFS vfs *vfs.VFS
f fs.Fs
handlers sftp.Handlers handlers sftp.Handlers
what string 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) fs.Debugf(c.what, "exec command: binary = %q, args = %q", binary, args)
switch binary { switch binary {
case "df": case "df":
about := c.f.Features().About about := c.vfs.Fs().Features().About
if about == nil { if about == nil {
return errors.New("df not supported") 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 // special cases for rclone command detection
switch args { switch args {
case "'abc' | md5sum": case "'abc' | md5sum":
if c.f.Hashes().Contains(hash.MD5) { if c.vfs.Fs().Hashes().Contains(hash.MD5) {
_, err = fmt.Fprintf(out, "0bee89b07a248e27c83fc3d5951213c1 -\n") _, err = fmt.Fprintf(out, "0bee89b07a248e27c83fc3d5951213c1 -\n")
if err != nil { if err != nil {
return errors.Wrap(err, "send output failed") 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") return errors.New("md5 hash not supported")
} }
case "'abc' | sha1sum": case "'abc' | sha1sum":
if c.f.Hashes().Contains(hash.SHA1) { if c.vfs.Fs().Hashes().Contains(hash.SHA1) {
_, err = fmt.Fprintf(out, "03cfd743661f07975fa2f1220c5194cbaff48451 -\n") _, err = fmt.Fprintf(out, "03cfd743661f07975fa2f1220c5194cbaff48451 -\n")
if err != nil { if err != nil {
return errors.Wrap(err, "send output failed") return errors.Wrap(err, "send output failed")

View File

@ -19,14 +19,14 @@ type vfsHandler struct {
} }
// vfsHandler returns a Handlers object with the test handlers. // 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} v := vfsHandler{VFS: vfs}
return sftp.Handlers{ return sftp.Handlers{
FileGet: v, FileGet: v,
FilePut: v, FilePut: v,
FileCmd: v, FileCmd: v,
FileList: v, FileList: v,
}, nil }
} }
func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) { func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {

View File

@ -18,7 +18,8 @@ import (
"strings" "strings"
"github.com/pkg/errors" "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"
"github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/lib/env" "github.com/rclone/rclone/lib/env"
@ -33,21 +34,47 @@ type server struct {
opt Options opt Options
vfs *vfs.VFS vfs *vfs.VFS
config *ssh.ServerConfig config *ssh.ServerConfig
handlers sftp.Handlers
listener net.Listener listener net.Listener
waitChan chan struct{} // for waiting on the listener to close waitChan chan struct{} // for waiting on the listener to close
proxy *proxy.Proxy
} }
func newServer(f fs.Fs, opt *Options) *server { func newServer(f fs.Fs, opt *Options) *server {
s := &server{ s := &server{
f: f, f: f,
vfs: vfs.New(f, &vfsflags.Opt),
opt: *opt, opt: *opt,
waitChan: make(chan struct{}), waitChan: make(chan struct{}),
} }
if proxyflags.Opt.AuthProxy != "" {
s.proxy = proxy.New(&proxyflags.Opt)
} else {
s.vfs = vfs.New(f, &vfsflags.Opt)
}
return s 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() { func (s *server) acceptConnections() {
for { for {
nConn, err := s.listener.Accept() nConn, err := s.listener.Accept()
@ -73,11 +100,15 @@ func (s *server) acceptConnections() {
go ssh.DiscardRequests(reqs) go ssh.DiscardRequests(reqs)
c := &conn{ c := &conn{
vfs: s.vfs,
f: s.f,
handlers: s.handlers,
what: what, 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 // Accept all channels
go c.handleChannels(chans) go c.handleChannels(chans)
@ -109,7 +140,19 @@ func (s *server) serve() (err error) {
ServerVersion: "SSH-2.0-" + fs.Config.UserAgent, ServerVersion: "SSH-2.0-" + fs.Config.UserAgent,
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
fs.Debugf(describeConn(c), "Password login attempt for %s", c.User()) 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)) userOK := subtle.ConstantTimeCompare([]byte(c.User()), []byte(s.opt.User))
passOK := subtle.ConstantTimeCompare(pass, []byte(s.opt.Pass)) passOK := subtle.ConstantTimeCompare(pass, []byte(s.opt.Pass))
if (userOK & passOK) == 1 { 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) { PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
fs.Debugf(describeConn(c), "Public key login attempt for %s", c.User()) 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 { if _, ok := authorizedKeysMap[string(pubKey.Marshal())]; ok {
return &ssh.Permissions{ return &ssh.Permissions{
// Record the public key used for authentication. // 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()) 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() go s.acceptConnections()
return nil return nil

View File

@ -6,6 +6,9 @@ package sftp
import ( import (
"github.com/rclone/rclone/cmd" "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/config/flags"
"github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fs/rc"
"github.com/rclone/rclone/vfs" "github.com/rclone/rclone/vfs"
@ -46,6 +49,7 @@ func AddFlags(flagSet *pflag.FlagSet, Opt *Options) {
func init() { func init() {
vfsflags.AddFlags(Command.Flags()) vfsflags.AddFlags(Command.Flags())
proxyflags.AddFlags(Command.Flags())
AddFlags(Command.Flags(), &Opt) 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 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. sftp backend, but it may not be with other SFTP clients.
` + vfs.Help, ` + vfs.Help + proxy.Help,
Run: func(command *cobra.Command, args []string) { Run: func(command *cobra.Command, args []string) {
var f fs.Fs
if proxyflags.Opt.AuthProxy == "" {
cmd.CheckArgs(1, 1, command, args) 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 { cmd.Run(false, true, command, func() error {
s := newServer(f, &Opt) s := newServer(f, &Opt)
err := s.Serve() err := s.Serve()