github.com/Jeffail/benthos/v3@v3.65.0/internal/impl/sftp/sftp.go (about)

     1  package sftp
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"os"
     7  
     8  	"github.com/Jeffail/benthos/v3/internal/docs"
     9  	"github.com/pkg/sftp"
    10  	"golang.org/x/crypto/ssh"
    11  )
    12  
    13  // CredentialsDocs returns a documentation field spec for SFTP credentials
    14  // fields within a Config.
    15  func CredentialsDocs() docs.FieldSpecs {
    16  	return docs.FieldSpecs{
    17  		docs.FieldCommon("username", "The username to connect to the SFTP server."),
    18  		docs.FieldCommon("password", "The password for the username to connect to the SFTP server."),
    19  		docs.FieldCommon("private_key_file", "The private key for the username to connect to the SFTP server."),
    20  		docs.FieldCommon("private_key_pass", "Optional passphrase for private key."),
    21  	}
    22  }
    23  
    24  // Credentials contains the credentials for connecting to the SFTP server
    25  type Credentials struct {
    26  	Username       string `json:"username" yaml:"username"`
    27  	Password       string `json:"password" yaml:"password"`
    28  	PrivateKeyFile string `json:"private_key_file" yaml:"private_key_file"`
    29  	PrivateKeyPass string `json:"private_key_pass" yaml:"private_key_pass"`
    30  }
    31  
    32  // GetClient establishes a fresh sftp client from a set of credentials and an
    33  // address.
    34  func (c Credentials) GetClient(address string) (*sftp.Client, error) {
    35  	host, port, err := net.SplitHostPort(address)
    36  	if err != nil {
    37  		return nil, fmt.Errorf("failed to parse address: %v", err)
    38  	}
    39  
    40  	// create sftp client and establish connection
    41  	server := &Server{
    42  		Host: host,
    43  		Port: port,
    44  	}
    45  
    46  	certCheck := &ssh.CertChecker{
    47  		IsHostAuthority: HostAuthCallback(),
    48  		IsRevoked:       CertCallback(server),
    49  		HostKeyFallback: HostCallback(server),
    50  	}
    51  
    52  	config := &ssh.ClientConfig{
    53  		User:            c.Username,
    54  		Auth:            []ssh.AuthMethod{},
    55  		HostKeyCallback: certCheck.CheckHostKey,
    56  	}
    57  
    58  	// set password auth when provided
    59  	if c.Password != "" {
    60  		// append to config.Auth
    61  		config.Auth = append(config.Auth, ssh.Password(c.Password))
    62  	}
    63  
    64  	// set private key auth when provided
    65  	if c.PrivateKeyFile != "" {
    66  		// read private key file
    67  		var privateKey []byte
    68  		privateKey, err = os.ReadFile(c.PrivateKeyFile)
    69  		if err != nil {
    70  			return nil, fmt.Errorf("failed to read private key: %v", err)
    71  		}
    72  		// check if passphrase is provided and parse private key
    73  		var signer ssh.Signer
    74  		if c.PrivateKeyPass == "" {
    75  			signer, err = ssh.ParsePrivateKey(privateKey)
    76  		} else {
    77  			signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(c.PrivateKeyPass))
    78  		}
    79  		if err != nil {
    80  			return nil, fmt.Errorf("failed to parse private key: %v", err)
    81  		}
    82  		// append to config.Auth
    83  		config.Auth = append(config.Auth, ssh.PublicKeys(signer))
    84  	}
    85  
    86  	conn, err := ssh.Dial("tcp", address, config)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	client, err := sftp.NewClient(conn)
    92  	if err != nil {
    93  		conn.Close()
    94  		return nil, err
    95  	}
    96  
    97  	return client, nil
    98  }
    99  
   100  // Server contains connection data for connecting to an SFTP server
   101  type Server struct {
   102  	Address   string          // host:port
   103  	Host      string          // IP address
   104  	Port      string          // port
   105  	IsSSH     bool            // true if server is running SSH on address:port
   106  	Banner    string          // banner text, if any
   107  	Cert      ssh.Certificate // server's certificate
   108  	Hostname  string          // hostname
   109  	PublicKey ssh.PublicKey   // server's public key
   110  }
   111  
   112  // HostAuthorityCallback used when setting up the connection to the SFTP server
   113  type HostAuthorityCallback func(ssh.PublicKey, string) bool
   114  
   115  // IsRevokedCallback used when setting up the connection to the SFTP server
   116  type IsRevokedCallback func(cert *ssh.Certificate) bool
   117  
   118  // HostAuthCallback is called when setting up the connection to the SFTP server
   119  func HostAuthCallback() HostAuthorityCallback {
   120  	return func(p ssh.PublicKey, addr string) bool {
   121  		return true
   122  	}
   123  }
   124  
   125  // CertCallback is called when setting up the connection to the SFTP server
   126  func CertCallback(s *Server) IsRevokedCallback {
   127  	return func(cert *ssh.Certificate) bool {
   128  		s.Cert = *cert
   129  		s.IsSSH = true
   130  
   131  		return false
   132  	}
   133  }
   134  
   135  // HostCallback is called when setting up the connection to the SFTP server
   136  func HostCallback(s *Server) ssh.HostKeyCallback {
   137  	return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
   138  		s.Hostname = hostname
   139  		s.PublicKey = key
   140  		return nil
   141  	}
   142  }