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 }