mirror of
https://github.com/rclone/rclone.git
synced 2025-06-26 06:51:36 +02:00
serve sftp: add serve rc interface
This commit is contained in:
parent
703788b40e
commit
5702b7578c
@ -16,6 +16,7 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -40,23 +41,27 @@ type server struct {
|
|||||||
ctx context.Context // for global config
|
ctx context.Context // for global config
|
||||||
config *ssh.ServerConfig
|
config *ssh.ServerConfig
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
waitChan chan struct{} // for waiting on the listener to close
|
stopped chan struct{} // for waiting on the listener to stop
|
||||||
proxy *proxy.Proxy
|
proxy *proxy.Proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer(ctx context.Context, f fs.Fs, opt *Options) *server {
|
func newServer(ctx context.Context, f fs.Fs, opt *Options, vfsOpt *vfscommon.Options, proxyOpt *proxy.Options) (*server, error) {
|
||||||
s := &server{
|
s := &server{
|
||||||
f: f,
|
f: f,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
opt: *opt,
|
opt: *opt,
|
||||||
waitChan: make(chan struct{}),
|
stopped: make(chan struct{}),
|
||||||
}
|
}
|
||||||
if proxy.Opt.AuthProxy != "" {
|
if proxy.Opt.AuthProxy != "" {
|
||||||
s.proxy = proxy.New(ctx, &proxy.Opt, &vfscommon.Opt)
|
s.proxy = proxy.New(ctx, proxyOpt, vfsOpt)
|
||||||
} else {
|
} else {
|
||||||
s.vfs = vfs.New(f, &vfscommon.Opt)
|
s.vfs = vfs.New(f, vfsOpt)
|
||||||
}
|
}
|
||||||
return s
|
err := s.configure()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("sftp configuration failed: %w", err)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVFS gets the vfs from s or the proxy
|
// getVFS gets the vfs from s or the proxy
|
||||||
@ -128,8 +133,10 @@ func (s *server) acceptConnections() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// configure the server
|
||||||
|
//
|
||||||
// Based on example server code from golang.org/x/crypto/ssh and server_standalone
|
// Based on example server code from golang.org/x/crypto/ssh and server_standalone
|
||||||
func (s *server) serve() (err error) {
|
func (s *server) configure() (err error) {
|
||||||
var authorizedKeysMap map[string]struct{}
|
var authorizedKeysMap map[string]struct{}
|
||||||
|
|
||||||
// ensure the user isn't trying to use conflicting flags
|
// ensure the user isn't trying to use conflicting flags
|
||||||
@ -292,42 +299,35 @@ func (s *server) serve() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve SFTP until the server is Shutdown
|
||||||
|
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.acceptConnections()
|
||||||
go s.acceptConnections()
|
close(s.stopped)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addr returns the address the server is listening on
|
// Addr returns the address the server is listening on
|
||||||
func (s *server) Addr() string {
|
func (s *server) Addr() net.Addr {
|
||||||
return s.listener.Addr().String()
|
return s.listener.Addr()
|
||||||
}
|
|
||||||
|
|
||||||
// Serve runs the sftp server in the background.
|
|
||||||
//
|
|
||||||
// Use s.Close() and s.Wait() to shutdown server
|
|
||||||
func (s *server) Serve() error {
|
|
||||||
err := s.serve()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait blocks while the listener is open.
|
// Wait blocks while the listener is open.
|
||||||
func (s *server) Wait() {
|
func (s *server) Wait() {
|
||||||
<-s.waitChan
|
<-s.stopped
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close shuts the running server down
|
// Shutdown shuts the running server down
|
||||||
func (s *server) Close() {
|
func (s *server) Shutdown() error {
|
||||||
err := s.listener.Close()
|
err := s.listener.Close()
|
||||||
if err != nil {
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
fs.Errorf(nil, "Error on closing SFTP server: %v", err)
|
err = nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
close(s.waitChan)
|
s.Wait()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPrivateKey(keyPath string) (ssh.Signer, error) {
|
func loadPrivateKey(keyPath string) (ssh.Signer, error) {
|
||||||
|
@ -5,15 +5,19 @@ package sftp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/rclone/rclone/cmd"
|
"github.com/rclone/rclone/cmd"
|
||||||
"github.com/rclone/rclone/cmd/serve"
|
"github.com/rclone/rclone/cmd/serve"
|
||||||
"github.com/rclone/rclone/cmd/serve/proxy"
|
"github.com/rclone/rclone/cmd/serve/proxy"
|
||||||
"github.com/rclone/rclone/cmd/serve/proxy/proxyflags"
|
"github.com/rclone/rclone/cmd/serve/proxy/proxyflags"
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/config/configstruct"
|
||||||
"github.com/rclone/rclone/fs/config/flags"
|
"github.com/rclone/rclone/fs/config/flags"
|
||||||
|
"github.com/rclone/rclone/fs/rc"
|
||||||
"github.com/rclone/rclone/lib/systemd"
|
"github.com/rclone/rclone/lib/systemd"
|
||||||
"github.com/rclone/rclone/vfs"
|
"github.com/rclone/rclone/vfs"
|
||||||
|
"github.com/rclone/rclone/vfs/vfscommon"
|
||||||
"github.com/rclone/rclone/vfs/vfsflags"
|
"github.com/rclone/rclone/vfs/vfsflags"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@ -78,6 +82,28 @@ func init() {
|
|||||||
proxyflags.AddFlags(Command.Flags())
|
proxyflags.AddFlags(Command.Flags())
|
||||||
AddFlags(Command.Flags(), &Opt)
|
AddFlags(Command.Flags(), &Opt)
|
||||||
serve.Command.AddCommand(Command)
|
serve.Command.AddCommand(Command)
|
||||||
|
serve.AddRc("sftp", func(ctx context.Context, f fs.Fs, in rc.Params) (serve.Handle, error) {
|
||||||
|
// Read VFS Opts
|
||||||
|
var vfsOpt = vfscommon.Opt // set default opts
|
||||||
|
err := configstruct.SetAny(in, &vfsOpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Read Proxy Opts
|
||||||
|
var proxyOpt = proxy.Opt // set default opts
|
||||||
|
err = configstruct.SetAny(in, &proxyOpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Read opts
|
||||||
|
var opt = Opt // set default opts
|
||||||
|
err = configstruct.SetAny(in, &opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Create server
|
||||||
|
return newServer(ctx, f, &opt, &vfsOpt, &proxyOpt)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command definition for cobra
|
// Command definition for cobra
|
||||||
@ -164,14 +190,12 @@ provided by OpenSSH in this case.
|
|||||||
if Opt.Stdio {
|
if Opt.Stdio {
|
||||||
return serveStdio(f)
|
return serveStdio(f)
|
||||||
}
|
}
|
||||||
s := newServer(context.Background(), f, &Opt)
|
s, err := newServer(context.Background(), f, &Opt, &vfscommon.Opt, &proxy.Opt)
|
||||||
err := s.Serve()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fs.Fatal(nil, fmt.Sprint(err))
|
||||||
}
|
}
|
||||||
defer systemd.Notify()()
|
defer systemd.Notify()()
|
||||||
s.Wait()
|
return s.Serve()
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,14 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
_ "github.com/rclone/rclone/backend/local"
|
_ "github.com/rclone/rclone/backend/local"
|
||||||
|
"github.com/rclone/rclone/cmd/serve/proxy"
|
||||||
"github.com/rclone/rclone/cmd/serve/servetest"
|
"github.com/rclone/rclone/cmd/serve/servetest"
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/config/configmap"
|
"github.com/rclone/rclone/fs/config/configmap"
|
||||||
"github.com/rclone/rclone/fs/config/obscure"
|
"github.com/rclone/rclone/fs/config/obscure"
|
||||||
|
"github.com/rclone/rclone/fs/rc"
|
||||||
|
"github.com/rclone/rclone/vfs/vfscommon"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,11 +49,14 @@ func TestSftp(t *testing.T) {
|
|||||||
opt.User = testUser
|
opt.User = testUser
|
||||||
opt.Pass = testPass
|
opt.Pass = testPass
|
||||||
|
|
||||||
w := newServer(context.Background(), f, &opt)
|
w, err := newServer(context.Background(), f, &opt, &vfscommon.Opt, &proxy.Opt)
|
||||||
require.NoError(t, w.serve())
|
require.NoError(t, err)
|
||||||
|
go func() {
|
||||||
|
require.NoError(t, w.Serve())
|
||||||
|
}()
|
||||||
|
|
||||||
// Read the host and port we started on
|
// Read the host and port we started on
|
||||||
addr := w.Addr()
|
addr := w.Addr().String()
|
||||||
colon := strings.LastIndex(addr, ":")
|
colon := strings.LastIndex(addr, ":")
|
||||||
|
|
||||||
// Config for the backend we'll use to connect to the server
|
// Config for the backend we'll use to connect to the server
|
||||||
@ -63,10 +70,18 @@ func TestSftp(t *testing.T) {
|
|||||||
|
|
||||||
// return a stop function
|
// return a stop function
|
||||||
return config, func() {
|
return config, func() {
|
||||||
w.Close()
|
assert.NoError(t, w.Shutdown())
|
||||||
w.Wait()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
servetest.Run(t, "sftp", start)
|
servetest.Run(t, "sftp", start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRc(t *testing.T) {
|
||||||
|
servetest.TestRc(t, rc.Params{
|
||||||
|
"type": "sftp",
|
||||||
|
"user": "test",
|
||||||
|
"pass": obscure.MustObscure("test"),
|
||||||
|
"vfs_cache_mode": "off",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user