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 }