diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go index 1234567..abcdefg 100644 --- a/cmd/litestream/main.go +++ b/cmd/litestream/main.go @@ -362,6 +362,7 @@ type ReplicaConfig struct { Host string `yaml:"host"` User string `yaml:"user"` Password string `yaml:"password"` KeyPath string `yaml:"key-path"` + HostKeyPath string `yaml:"host-key-path"` // Encryption identities and recipients @@ -664,6 +665,7 @@ func NewReplicaFromConfig(c *ReplicaConfig, dbc *DBConfig) (_ litestream.Replic client.Password = password client.Path = path client.KeyPath = c.KeyPath + client.HostKeyPath = c.HostKeyPath return client, nil } diff --git a/sftp/replica_client.go b/sftp/replica_client.go index 30d8fa87..8b651e97 100644 --- a/sftp/replica_client.go +++ b/sftp/replica_client.go @@ -41,6 +41,7 @@ type ReplicaClient struct { Password string Path string KeyPath string + HostKeyPath string DialTimeout time.Duration } @@ -71,14 +72,28 @@ func (c *ReplicaClient) Init(ctx context.Context) (_ *sftp.Client, err error) { // Build SSH configuration & auth methods config := &ssh.ClientConfig{ - User: c.User, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - BannerCallback: ssh.BannerDisplayStderr(), + User: c.User, + BannerCallback: ssh.BannerDisplayStderr(), } if c.Password != "" { config.Auth = append(config.Auth, ssh.Password(c.Password)) } + if c.HostKeyPath == "" { + config.HostKeyCallback = ssh.InsecureIgnoreHostKey() + } else { + buf, err := os.ReadFile(c.HostKeyPath) + if err != nil { + return nil, fmt.Errorf("cannot read sftp host key path: %w", err) + } + + key, _, _, _, err := ssh.ParseAuthorizedKey(buf) + if err != nil { + return nil, fmt.Errorf("cannot parse sftp host key path: path=%s len=%d err=%w", c.HostKeyPath, len(buf), err) + } + config.HostKeyCallback = ssh.FixedHostKey(key) + } + if c.KeyPath != "" { buf, err := os.ReadFile(c.KeyPath) if err != nil {