github.com/divyam234/rclone@v1.64.1/cmd/serve/sftp/server.go (about)

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