github.com/artpar/rclone@v1.67.3/cmd/serve/sftp/server.go (about)

     1  //go:build !plan9
     2  
     3  package sftp
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"crypto/ecdsa"
     9  	"crypto/ed25519"
    10  	"crypto/elliptic"
    11  	"crypto/rand"
    12  	"crypto/rsa"
    13  	"crypto/subtle"
    14  	"crypto/x509"
    15  	"encoding/base64"
    16  	"encoding/pem"
    17  	"errors"
    18  	"fmt"
    19  	"net"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"github.com/artpar/rclone/cmd/serve/proxy"
    25  	"github.com/artpar/rclone/cmd/serve/proxy/proxyflags"
    26  	"github.com/artpar/rclone/fs"
    27  	"github.com/artpar/rclone/fs/config"
    28  	"github.com/artpar/rclone/lib/env"
    29  	"github.com/artpar/rclone/lib/file"
    30  	"github.com/artpar/rclone/vfs"
    31  	"github.com/artpar/rclone/vfs/vfsflags"
    32  	"golang.org/x/crypto/ssh"
    33  )
    34  
    35  // server contains everything to run the server
    36  type server struct {
    37  	f        fs.Fs
    38  	opt      Options
    39  	vfs      *vfs.VFS
    40  	ctx      context.Context // for global config
    41  	config   *ssh.ServerConfig
    42  	listener net.Listener
    43  	waitChan chan struct{} // for waiting on the listener to close
    44  	proxy    *proxy.Proxy
    45  }
    46  
    47  func newServer(ctx context.Context, f fs.Fs, opt *Options) *server {
    48  	s := &server{
    49  		f:        f,
    50  		ctx:      ctx,
    51  		opt:      *opt,
    52  		waitChan: make(chan struct{}),
    53  	}
    54  	if proxyflags.Opt.AuthProxy != "" {
    55  		s.proxy = proxy.New(ctx, &proxyflags.Opt)
    56  	} else {
    57  		s.vfs = vfs.New(f, &vfsflags.Opt)
    58  	}
    59  	return s
    60  }
    61  
    62  // getVFS gets the vfs from s or the proxy
    63  func (s *server) getVFS(what string, sshConn *ssh.ServerConn) (VFS *vfs.VFS) {
    64  	if s.proxy == nil {
    65  		return s.vfs
    66  	}
    67  	if sshConn.Permissions == nil && sshConn.Permissions.Extensions == nil {
    68  		fs.Infof(what, "SSH Permissions Extensions not found")
    69  		return nil
    70  	}
    71  	key := sshConn.Permissions.Extensions["_vfsKey"]
    72  	if key == "" {
    73  		fs.Infof(what, "VFS key not found")
    74  		return nil
    75  	}
    76  	VFS = s.proxy.Get(key)
    77  	if VFS == nil {
    78  		fs.Infof(what, "failed to read VFS from cache")
    79  		return nil
    80  	}
    81  	return VFS
    82  }
    83  
    84  // Accept a single connection - run in a go routine as the ssh
    85  // authentication can block
    86  func (s *server) acceptConnection(nConn net.Conn) {
    87  	what := describeConn(nConn)
    88  
    89  	// Before use, a handshake must be performed on the incoming net.Conn.
    90  	sshConn, chans, reqs, err := ssh.NewServerConn(nConn, s.config)
    91  	if err != nil {
    92  		fs.Errorf(what, "SSH login failed: %v", err)
    93  		return
    94  	}
    95  
    96  	fs.Infof(what, "SSH login from %s using %s", sshConn.User(), sshConn.ClientVersion())
    97  
    98  	// Discard all global out-of-band Requests
    99  	go ssh.DiscardRequests(reqs)
   100  
   101  	c := &conn{
   102  		what: what,
   103  		vfs:  s.getVFS(what, sshConn),
   104  	}
   105  	if c.vfs == nil {
   106  		fs.Infof(what, "Closing unauthenticated connection (couldn't find VFS)")
   107  		_ = nConn.Close()
   108  		return
   109  	}
   110  	c.handlers = newVFSHandler(c.vfs)
   111  
   112  	// Accept all channels
   113  	go c.handleChannels(chans)
   114  }
   115  
   116  // Accept connections and call them in a go routine
   117  func (s *server) acceptConnections() {
   118  	for {
   119  		nConn, err := s.listener.Accept()
   120  		if err != nil {
   121  			if strings.Contains(err.Error(), "use of closed network connection") {
   122  				return
   123  			}
   124  			fs.Errorf(nil, "Failed to accept incoming connection: %v", err)
   125  			continue
   126  		}
   127  		go s.acceptConnection(nConn)
   128  	}
   129  }
   130  
   131  // Based on example server code from golang.org/x/crypto/ssh and server_standalone
   132  func (s *server) serve() (err error) {
   133  	var authorizedKeysMap map[string]struct{}
   134  
   135  	// ensure the user isn't trying to use conflicting flags
   136  	if proxyflags.Opt.AuthProxy != "" && s.opt.AuthorizedKeys != "" && s.opt.AuthorizedKeys != DefaultOpt.AuthorizedKeys {
   137  		return errors.New("--auth-proxy and --authorized-keys cannot be used at the same time")
   138  	}
   139  
   140  	// Load the authorized keys
   141  	if s.opt.AuthorizedKeys != "" && proxyflags.Opt.AuthProxy == "" {
   142  		authKeysFile := env.ShellExpand(s.opt.AuthorizedKeys)
   143  		authorizedKeysMap, err = loadAuthorizedKeys(authKeysFile)
   144  		// If user set the flag away from the default then report an error
   145  		if err != nil && s.opt.AuthorizedKeys != DefaultOpt.AuthorizedKeys {
   146  			return err
   147  		}
   148  		fs.Logf(nil, "Loaded %d authorized keys from %q", len(authorizedKeysMap), authKeysFile)
   149  	}
   150  
   151  	if !s.opt.NoAuth && len(authorizedKeysMap) == 0 && s.opt.User == "" && s.opt.Pass == "" && s.proxy == nil {
   152  		return errors.New("no authorization found, use --user/--pass or --authorized-keys or --no-auth or --auth-proxy")
   153  	}
   154  
   155  	// An SSH server is represented by a ServerConfig, which holds
   156  	// certificate details and handles authentication of ServerConns.
   157  	s.config = &ssh.ServerConfig{
   158  		ServerVersion: "SSH-2.0-" + fs.GetConfig(s.ctx).UserAgent,
   159  		PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
   160  			fs.Debugf(describeConn(c), "Password login attempt for %s", c.User())
   161  			if s.proxy != nil {
   162  				// query the proxy for the config
   163  				_, vfsKey, err := s.proxy.Call(c.User(), string(pass), false)
   164  				if err != nil {
   165  					return nil, err
   166  				}
   167  				// just return the Key so we can get it back from the cache
   168  				return &ssh.Permissions{
   169  					Extensions: map[string]string{
   170  						"_vfsKey": vfsKey,
   171  					},
   172  				}, nil
   173  			} else if s.opt.User != "" && s.opt.Pass != "" {
   174  				userOK := subtle.ConstantTimeCompare([]byte(c.User()), []byte(s.opt.User))
   175  				passOK := subtle.ConstantTimeCompare(pass, []byte(s.opt.Pass))
   176  				if (userOK & passOK) == 1 {
   177  					return nil, nil
   178  				}
   179  			}
   180  			return nil, fmt.Errorf("password rejected for %q", c.User())
   181  		},
   182  		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
   183  			fs.Debugf(describeConn(c), "Public key login attempt for %s", c.User())
   184  			if s.proxy != nil {
   185  				//query the proxy for the config
   186  				_, vfsKey, err := s.proxy.Call(
   187  					c.User(),
   188  					base64.StdEncoding.EncodeToString(pubKey.Marshal()),
   189  					true,
   190  				)
   191  				if err != nil {
   192  					return nil, err
   193  				}
   194  				// just return the Key so we can get it back from the cache
   195  				return &ssh.Permissions{
   196  					Extensions: map[string]string{
   197  						"_vfsKey": vfsKey,
   198  					},
   199  				}, nil
   200  			}
   201  			if _, ok := authorizedKeysMap[string(pubKey.Marshal())]; ok {
   202  				return &ssh.Permissions{
   203  					// Record the public key used for authentication.
   204  					Extensions: map[string]string{
   205  						"pubkey-fp": ssh.FingerprintSHA256(pubKey),
   206  					},
   207  				}, nil
   208  			}
   209  			return nil, fmt.Errorf("unknown public key for %q", c.User())
   210  		},
   211  		AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
   212  			status := "OK"
   213  			if err != nil {
   214  				status = err.Error()
   215  			}
   216  			fs.Debugf(describeConn(conn), "ssh auth %q from %q: %s", method, conn.ClientVersion(), status)
   217  		},
   218  		NoClientAuth: s.opt.NoAuth,
   219  	}
   220  
   221  	// Load the private key, from the cache if not explicitly configured
   222  	keyPaths := s.opt.HostKeys
   223  	cachePath := filepath.Join(config.GetCacheDir(), "serve-sftp")
   224  	if len(keyPaths) == 0 {
   225  		keyPaths = []string{
   226  			filepath.Join(cachePath, "id_rsa"),
   227  			filepath.Join(cachePath, "id_ecdsa"),
   228  			filepath.Join(cachePath, "id_ed25519"),
   229  		}
   230  	}
   231  	for _, keyPath := range keyPaths {
   232  		private, err := loadPrivateKey(keyPath)
   233  		if err != nil && len(s.opt.HostKeys) == 0 {
   234  			fs.Debugf(nil, "Failed to load %q: %v", keyPath, err)
   235  			// If loading a cached key failed, make the keys and retry
   236  			err = file.MkdirAll(cachePath, 0700)
   237  			if err != nil {
   238  				return fmt.Errorf("failed to create cache path: %w", err)
   239  			}
   240  			if strings.HasSuffix(keyPath, string(os.PathSeparator)+"id_rsa") {
   241  				const bits = 2048
   242  				fs.Logf(nil, "Generating %d bit key pair at %q", bits, keyPath)
   243  				err = makeRSASSHKeyPair(bits, keyPath+".pub", keyPath)
   244  			} else if strings.HasSuffix(keyPath, string(os.PathSeparator)+"id_ecdsa") {
   245  				fs.Logf(nil, "Generating ECDSA p256 key pair at %q", keyPath)
   246  				err = makeECDSASSHKeyPair(keyPath+".pub", keyPath)
   247  			} else if strings.HasSuffix(keyPath, string(os.PathSeparator)+"id_ed25519") {
   248  				fs.Logf(nil, "Generating Ed25519 key pair at %q", keyPath)
   249  				err = makeEd25519SSHKeyPair(keyPath+".pub", keyPath)
   250  			} else {
   251  				return fmt.Errorf("don't know how to generate key pair %q", keyPath)
   252  			}
   253  			if err != nil {
   254  				return fmt.Errorf("failed to create SSH key pair: %w", err)
   255  			}
   256  			// reload the new key
   257  			private, err = loadPrivateKey(keyPath)
   258  		}
   259  		if err != nil {
   260  			return err
   261  		}
   262  		fs.Debugf(nil, "Loaded private key from %q", keyPath)
   263  
   264  		s.config.AddHostKey(private)
   265  	}
   266  
   267  	// Once a ServerConfig has been configured, connections can be
   268  	// accepted.
   269  	s.listener, err = net.Listen("tcp", s.opt.ListenAddr)
   270  	if err != nil {
   271  		return fmt.Errorf("failed to listen for connection: %w", err)
   272  	}
   273  	fs.Logf(nil, "SFTP server listening on %v\n", s.listener.Addr())
   274  
   275  	go s.acceptConnections()
   276  
   277  	return nil
   278  }
   279  
   280  // Addr returns the address the server is listening on
   281  func (s *server) Addr() string {
   282  	return s.listener.Addr().String()
   283  }
   284  
   285  // Serve runs the sftp server in the background.
   286  //
   287  // Use s.Close() and s.Wait() to shutdown server
   288  func (s *server) Serve() error {
   289  	err := s.serve()
   290  	if err != nil {
   291  		return err
   292  	}
   293  	return nil
   294  }
   295  
   296  // Wait blocks while the listener is open.
   297  func (s *server) Wait() {
   298  	<-s.waitChan
   299  }
   300  
   301  // Close shuts the running server down
   302  func (s *server) Close() {
   303  	err := s.listener.Close()
   304  	if err != nil {
   305  		fs.Errorf(nil, "Error on closing SFTP server: %v", err)
   306  		return
   307  	}
   308  	close(s.waitChan)
   309  }
   310  
   311  func loadPrivateKey(keyPath string) (ssh.Signer, error) {
   312  	privateBytes, err := os.ReadFile(keyPath)
   313  	if err != nil {
   314  		return nil, fmt.Errorf("failed to load private key: %w", err)
   315  	}
   316  	private, err := ssh.ParsePrivateKey(privateBytes)
   317  	if err != nil {
   318  		return nil, fmt.Errorf("failed to parse private key: %w", err)
   319  	}
   320  	return private, nil
   321  }
   322  
   323  // Public key authentication is done by comparing
   324  // the public key of a received connection
   325  // with the entries in the authorized_keys file.
   326  func loadAuthorizedKeys(authorizedKeysPath string) (authorizedKeysMap map[string]struct{}, err error) {
   327  	authorizedKeysBytes, err := os.ReadFile(authorizedKeysPath)
   328  	if err != nil {
   329  		return nil, fmt.Errorf("failed to load authorized keys: %w", err)
   330  	}
   331  	authorizedKeysMap = make(map[string]struct{})
   332  	for len(authorizedKeysBytes) > 0 {
   333  		pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
   334  		if err != nil {
   335  			return nil, fmt.Errorf("failed to parse authorized keys: %w", err)
   336  		}
   337  		authorizedKeysMap[string(pubKey.Marshal())] = struct{}{}
   338  		authorizedKeysBytes = bytes.TrimSpace(rest)
   339  	}
   340  	return authorizedKeysMap, nil
   341  }
   342  
   343  // makeRSASSHKeyPair make a pair of public and private keys for SSH access.
   344  // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
   345  // Private Key generated is PEM encoded
   346  //
   347  // Originally from: https://stackoverflow.com/a/34347463/164234
   348  func makeRSASSHKeyPair(bits int, pubKeyPath, privateKeyPath string) (err error) {
   349  	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
   350  	if err != nil {
   351  		return err
   352  	}
   353  
   354  	// generate and write private key as PEM
   355  	privateKeyFile, err := os.OpenFile(privateKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   356  	if err != nil {
   357  		return err
   358  	}
   359  	defer fs.CheckClose(privateKeyFile, &err)
   360  	privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
   361  	if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
   362  		return err
   363  	}
   364  
   365  	// generate and write public key
   366  	pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
   367  	if err != nil {
   368  		return err
   369  	}
   370  	return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
   371  }
   372  
   373  // makeECDSASSHKeyPair make a pair of public and private keys for ECDSA SSH access.
   374  // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
   375  // Private Key generated is PEM encoded
   376  func makeECDSASSHKeyPair(pubKeyPath, privateKeyPath string) (err error) {
   377  	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	// generate and write private key as PEM
   383  	privateKeyFile, err := os.OpenFile(privateKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   384  	if err != nil {
   385  		return err
   386  	}
   387  	defer fs.CheckClose(privateKeyFile, &err)
   388  	buf, err := x509.MarshalECPrivateKey(privateKey)
   389  	if err != nil {
   390  		return err
   391  	}
   392  	privateKeyPEM := &pem.Block{Type: "EC PRIVATE KEY", Bytes: buf}
   393  	if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
   394  		return err
   395  	}
   396  
   397  	// generate and write public key
   398  	pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
   399  	if err != nil {
   400  		return err
   401  	}
   402  	return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
   403  }
   404  
   405  // makeEd25519SSHKeyPair make a pair of public and private keys for Ed25519 SSH access.
   406  // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
   407  // Private Key generated is PEM encoded
   408  func makeEd25519SSHKeyPair(pubKeyPath, privateKeyPath string) (err error) {
   409  	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
   410  	if err != nil {
   411  		return err
   412  	}
   413  
   414  	// generate and write private key as PEM
   415  	privateKeyFile, err := os.OpenFile(privateKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   416  	if err != nil {
   417  		return err
   418  	}
   419  	defer fs.CheckClose(privateKeyFile, &err)
   420  	buf, err := x509.MarshalPKCS8PrivateKey(privateKey)
   421  	if err != nil {
   422  		return err
   423  	}
   424  	privateKeyPEM := &pem.Block{Type: "PRIVATE KEY", Bytes: buf}
   425  	if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
   426  		return err
   427  	}
   428  
   429  	// generate and write public key
   430  	pub, err := ssh.NewPublicKey(publicKey)
   431  	if err != nil {
   432  		return err
   433  	}
   434  	return os.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0644)
   435  }