From 55c1efd75d08995c5094a6c1e9872d130baa47f2 Mon Sep 17 00:00:00 2001 From: Kevin Barbour Date: Thu, 27 Jun 2024 15:56:47 -0400 Subject: [PATCH] Add client SFTP file support and fix docs The config documentation claims that `ClientPrivateKey` should be a filename but in reality it needs to be the raw key string. I've fixed that documentation, but also added a `ClientPrivateKeyFile` config for flexibility with sourcing the client private key from the filesystem. --- docs/config.md | 3 ++- internal/service/model_upload.go | 22 +++++++++++++--------- internal/upload/sftp.go | 13 ++++++++++++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/config.md b/docs/config.md index 3760ab7..bd1dd68 100644 --- a/docs/config.md +++ b/docs/config.md @@ -244,7 +244,8 @@ ACHGateway: Hostname: Username: [ Password: ] - [ ClientPrivateKey: ] + [ ClientPrivateKey: ] + [ ClientPrivateKeyFile: ] [ HostPublicKey: ] [ DialTimeout: | default = 10s ] [ MaxConnectionsPerFile: | default = 1 ] diff --git a/internal/service/model_upload.go b/internal/service/model_upload.go index b568e93..4c200f4 100644 --- a/internal/service/model_upload.go +++ b/internal/service/model_upload.go @@ -136,9 +136,10 @@ type SFTP struct { Hostname string Username string - Password string - ClientPrivateKey string - HostPublicKey string + Password string + ClientPrivateKey string + ClientPrivateKeyFile string + HostPublicKey string DialTimeout time.Duration MaxConnectionsPerFile int @@ -154,9 +155,10 @@ func (cfg *SFTP) MarshalJSON() ([]byte, error) { Hostname string Username string - Password string - ClientPrivateKey string - HostPublicKey string + Password string + ClientPrivateKey string + ClientPrivateKeyFile string + HostPublicKey string DialTimeout time.Duration MaxConnectionsPerFile int @@ -168,9 +170,10 @@ func (cfg *SFTP) MarshalJSON() ([]byte, error) { Hostname: cfg.Hostname, Username: cfg.Username, - Password: mask.Password(cfg.Password), - ClientPrivateKey: cfg.ClientPrivateKey, - HostPublicKey: cfg.HostPublicKey, + Password: mask.Password(cfg.Password), + ClientPrivateKey: cfg.ClientPrivateKey, + ClientPrivateKeyFile: cfg.ClientPrivateKeyFile, + HostPublicKey: cfg.HostPublicKey, DialTimeout: cfg.DialTimeout, MaxConnectionsPerFile: cfg.MaxConnectionsPerFile, @@ -207,6 +210,7 @@ func (cfg *SFTP) String() string { buf.WriteString(fmt.Sprintf("Username=%s, ", cfg.Username)) buf.WriteString(fmt.Sprintf("Password=%s, ", mask.Password(cfg.Password))) buf.WriteString(fmt.Sprintf("ClientPrivateKey:%v, ", cfg.ClientPrivateKey != "")) + buf.WriteString(fmt.Sprintf("ClientPrivateKeyFile:%v, ", cfg.ClientPrivateKeyFile != "")) buf.WriteString(fmt.Sprintf("HostPublicKey:%v}, ", cfg.HostPublicKey != "")) return buf.String() } diff --git a/internal/upload/sftp.go b/internal/upload/sftp.go index 43bdf77..7db6703 100644 --- a/internal/upload/sftp.go +++ b/internal/upload/sftp.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "os" "path/filepath" "strings" @@ -34,12 +35,22 @@ func newSFTPTransferAgent(logger log.Logger, cfg *service.UploadAgent) (*SFTPTra return nil, fmt.Errorf("sftp: %s is not whitelisted: %v", cfg.SFTP.Hostname, err) } + clientPrivateKey := cfg.SFTP.ClientPrivateKey + + if clientPrivateKey == "" && cfg.SFTP.ClientPrivateKeyFile != "" { + key, err := os.ReadFile(cfg.SFTP.ClientPrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("sftp: unable to read private key file %s: %v", cfg.SFTP.ClientPrivateKeyFile, err) + } + clientPrivateKey = string(key) + } + client, err := go_sftp.NewClient(logger, &go_sftp.ClientConfig{ Hostname: cfg.SFTP.Hostname, Username: cfg.SFTP.Username, Password: cfg.SFTP.Password, - ClientPrivateKey: cfg.SFTP.ClientPrivateKey, + ClientPrivateKey: clientPrivateKey, HostPublicKey: cfg.SFTP.HostPublicKey, Timeout: cfg.SFTP.DialTimeout,