mirror of
https://github.com/rclone/rclone.git
synced 2024-11-08 09:35:26 +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
|
// 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")
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user