diff --git a/go.mod b/go.mod index 1a42550..6df1f36 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ go 1.14 require ( github.com/appleboy/com v0.0.6 - github.com/appleboy/easyssh-proxy v1.3.4 + github.com/appleboy/easyssh-proxy v1.3.5 github.com/fatih/color v1.9.0 github.com/joho/godotenv v1.3.0 github.com/stretchr/testify v1.5.1 github.com/urfave/cli/v2 v2.2.0 + golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 ) diff --git a/go.sum b/go.sum index d350f2a..f66f641 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681 h1:JS2rl38kZmHgWa0 github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681/go.mod h1:WfDateMPQ/55dPbZRp5Zxrux5WiEaHsjk9puUhz0KgY= github.com/appleboy/com v0.0.6 h1:l8cZ0aQJU/SWyL79ciYAJeqV835PRdlZ6efiPhus5Ic= github.com/appleboy/com v0.0.6/go.mod h1:jnufjIC3opMlReyPPPye+8JqNvUzLm25o7h6SOy8nv0= -github.com/appleboy/easyssh-proxy v1.3.4 h1:yNgzsJ9qaDNGzQILDXEK4boioJMmUUaTUsxYtCTSGqo= -github.com/appleboy/easyssh-proxy v1.3.4/go.mod h1:Kk57I3w7OCafOjp5kgZFvxk2fO8Tca5CriBTOsbSbjY= +github.com/appleboy/easyssh-proxy v1.3.5 h1:EGTCbqAVRcGKHQMFSxz30lQmb+0nXL+jUiCrg/FjHQM= +github.com/appleboy/easyssh-proxy v1.3.5/go.mod h1:Kk57I3w7OCafOjp5kgZFvxk2fO8Tca5CriBTOsbSbjY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= diff --git a/main.go b/main.go index 6829adf..0514eaa 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,11 @@ func main() { EnvVars: []string{"PLUGIN_CIPHERS", "SSH_CIPHERS", "CIPHERS", "INPUT_CIPHERS"}, Value: cli.NewStringSlice(defaultCiphers...), }, + &cli.StringFlag{ + Name: "fingerprint", + Usage: "fingerprint SHA256 of the host public key, default is to skip verification", + EnvVars: []string{"PLUGIN_FINGERPRINT", "SSH_FINGERPRINT", "FINGERPRINT", "INPUT_FINGERPRINT"}, + }, &cli.DurationFlag{ Name: "timeout", Usage: "connection timeout", @@ -198,6 +203,11 @@ func main() { EnvVars: []string{"PLUGIN_PROXY_CIPHERS", "PROXY_SSH_CIPHERS", "PROXY_CIPHERS", "INPUT_PROXY_CIPHERS"}, Value: cli.NewStringSlice(defaultCiphers...), }, + &cli.StringFlag{ + Name: "proxy.fingerprint", + Usage: "fingerprint SHA256 of the host public key, default is to skip verification", + EnvVars: []string{"PLUGIN_PROXY_FINGERPRINT", "SSH_PROXY_FINGERPRINT", "PROXY_FINGERPRINT", "INPUT_PROXY_FINGERPRINT"}, + }, &cli.StringFlag{ Name: "proxy.port", Usage: "connect to port of proxy", @@ -297,6 +307,7 @@ func run(c *cli.Context) error { Username: c.String("username"), Password: c.String("password"), Passphrase: c.String("ssh-passphrase"), + Fingerprint: c.String("fingerprint"), Timeout: c.Duration("timeout"), CommandTimeout: c.Duration("command.timeout"), Key: c.String("ssh-key"), @@ -311,15 +322,16 @@ func run(c *cli.Context) error { Overwrite: c.Bool("overwrite"), Ciphers: c.StringSlice("ciphers"), Proxy: easyssh.DefaultConfig{ - Key: c.String("proxy.ssh-key"), - Passphrase: c.String("proxy.ssh-passphrase"), - KeyPath: c.String("proxy.key-path"), - User: c.String("proxy.username"), - Password: c.String("proxy.password"), - Server: c.String("proxy.host"), - Port: c.String("proxy.port"), - Timeout: c.Duration("proxy.timeout"), - Ciphers: c.StringSlice("proxy.ciphers"), + Key: c.String("proxy.ssh-key"), + Passphrase: c.String("proxy.ssh-passphrase"), + Fingerprint: c.String("proxy.fingerprint"), + KeyPath: c.String("proxy.key-path"), + User: c.String("proxy.username"), + Password: c.String("proxy.password"), + Server: c.String("proxy.host"), + Port: c.String("proxy.port"), + Timeout: c.Duration("proxy.timeout"), + Ciphers: c.StringSlice("proxy.ciphers"), }, }, } diff --git a/plugin.go b/plugin.go index c0aa581..3781e2b 100644 --- a/plugin.go +++ b/plugin.go @@ -51,6 +51,7 @@ type ( Password string Key string Passphrase string + Fingerprint string KeyPath string Timeout time.Duration CommandTimeout time.Duration @@ -166,23 +167,25 @@ func (p *Plugin) removeDestFile(ssh *easyssh.MakeConfig) error { func (p *Plugin) removeAllDestFile() error { for _, host := range p.Config.Host { ssh := &easyssh.MakeConfig{ - Server: host, - User: p.Config.Username, - Password: p.Config.Password, - Port: p.Config.Port, - Key: p.Config.Key, - KeyPath: p.Config.KeyPath, - Passphrase: p.Config.Passphrase, - Timeout: p.Config.Timeout, + Server: host, + User: p.Config.Username, + Password: p.Config.Password, + Port: p.Config.Port, + Key: p.Config.Key, + KeyPath: p.Config.KeyPath, + Passphrase: p.Config.Passphrase, + Timeout: p.Config.Timeout, + Fingerprint: p.Config.Fingerprint, Proxy: easyssh.DefaultConfig{ - Server: p.Config.Proxy.Server, - User: p.Config.Proxy.User, - Password: p.Config.Proxy.Password, - Port: p.Config.Proxy.Port, - Key: p.Config.Proxy.Key, - KeyPath: p.Config.Proxy.KeyPath, - Passphrase: p.Config.Proxy.Passphrase, - Timeout: p.Config.Proxy.Timeout, + Server: p.Config.Proxy.Server, + User: p.Config.Proxy.User, + Password: p.Config.Proxy.Password, + Port: p.Config.Proxy.Port, + Key: p.Config.Proxy.Key, + KeyPath: p.Config.Proxy.KeyPath, + Passphrase: p.Config.Proxy.Passphrase, + Timeout: p.Config.Proxy.Timeout, + Fingerprint: p.Config.Proxy.Fingerprint, }, } @@ -276,25 +279,27 @@ func (p *Plugin) Exec() error { go func(host string) { // Create MakeConfig instance with remote username, server address and path to private key. ssh := &easyssh.MakeConfig{ - Server: host, - User: p.Config.Username, - Password: p.Config.Password, - Port: p.Config.Port, - Key: p.Config.Key, - KeyPath: p.Config.KeyPath, - Passphrase: p.Config.Passphrase, - Timeout: p.Config.Timeout, - Ciphers: p.Config.Ciphers, + Server: host, + User: p.Config.Username, + Password: p.Config.Password, + Port: p.Config.Port, + Key: p.Config.Key, + KeyPath: p.Config.KeyPath, + Passphrase: p.Config.Passphrase, + Timeout: p.Config.Timeout, + Ciphers: p.Config.Ciphers, + Fingerprint: p.Config.Fingerprint, Proxy: easyssh.DefaultConfig{ - Server: p.Config.Proxy.Server, - User: p.Config.Proxy.User, - Password: p.Config.Proxy.Password, - Port: p.Config.Proxy.Port, - Key: p.Config.Proxy.Key, - KeyPath: p.Config.Proxy.KeyPath, - Passphrase: p.Config.Proxy.Passphrase, - Timeout: p.Config.Proxy.Timeout, - Ciphers: p.Config.Proxy.Ciphers, + Server: p.Config.Proxy.Server, + User: p.Config.Proxy.User, + Password: p.Config.Proxy.Password, + Port: p.Config.Proxy.Port, + Key: p.Config.Proxy.Key, + KeyPath: p.Config.Proxy.KeyPath, + Passphrase: p.Config.Proxy.Passphrase, + Timeout: p.Config.Proxy.Timeout, + Ciphers: p.Config.Proxy.Ciphers, + Fingerprint: p.Config.Proxy.Fingerprint, }, } diff --git a/plugin_test.go b/plugin_test.go index f2522bc..b02aab6 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -1,6 +1,8 @@ package main import ( + "io/ioutil" + "log" "os" "os/exec" "os/user" @@ -11,6 +13,7 @@ import ( "github.com/appleboy/easyssh-proxy" "github.com/stretchr/testify/assert" + "golang.org/x/crypto/ssh" ) func TestMissingAllConfig(t *testing.T) { @@ -169,6 +172,90 @@ func TestSCPFileFromPublicKeyWithPassphrase(t *testing.T) { } } +func TestWrongFingerprint(t *testing.T) { + u, err := user.Lookup("drone-scp") + if err != nil { + t.Fatalf("Lookup: %v", err) + } + + plugin := Plugin{ + Config: Config{ + Host: []string{"localhost"}, + Username: "drone-scp", + Port: "22", + KeyPath: "./tests/.ssh/id_rsa", + Source: []string{"tests/a.txt", "tests/b.txt"}, + Target: []string{filepath.Join(u.HomeDir, "/test2")}, + CommandTimeout: 60 * time.Second, + TarExec: "tar", + Fingerprint: "wrong", + }, + } + + err = plugin.Exec() + log.Println(err) + assert.NotNil(t, err) +} + +func getHostPublicKeyFile(keypath string) (ssh.PublicKey, error) { + var pubkey ssh.PublicKey + var err error + buf, err := ioutil.ReadFile(keypath) + if err != nil { + return nil, err + } + + pubkey, _, _, _, err = ssh.ParseAuthorizedKey(buf) + + if err != nil { + return nil, err + } + + return pubkey, nil +} + +func TestSCPFileFromPublicKeyWithFingerprint(t *testing.T) { + if os.Getenv("SSH_AUTH_SOCK") != "" { + if err := exec.Command("eval", "`ssh-agent -k`").Run(); err != nil { + t.Fatalf("exec: %v", err) + } + } + + u, err := user.Lookup("drone-scp") + if err != nil { + t.Fatalf("Lookup: %v", err) + } + + hostKey, err := getHostPublicKeyFile("/etc/ssh/ssh_host_rsa_key.pub") + assert.NoError(t, err) + + plugin := Plugin{ + Config: Config{ + Host: []string{"localhost"}, + Username: "drone-scp", + Port: "22", + KeyPath: "./tests/.ssh/id_rsa", + Fingerprint: ssh.FingerprintSHA256(hostKey), + Source: []string{"tests/a.txt", "tests/b.txt"}, + Target: []string{filepath.Join(u.HomeDir, "/test2")}, + CommandTimeout: 60 * time.Second, + TarExec: "tar", + }, + } + + err = plugin.Exec() + assert.Nil(t, err) + + // check file exist + if _, err := os.Stat(filepath.Join(u.HomeDir, "/test2/tests/a.txt")); os.IsNotExist(err) { + t.Fatalf("SCP-error: %v", err) + } + + if _, err := os.Stat(filepath.Join(u.HomeDir, "/test2/tests/b.txt")); os.IsNotExist(err) { + t.Fatalf("SCP-error: %v", err) + } +} + func TestSCPWildcardFileList(t *testing.T) { if os.Getenv("SSH_AUTH_SOCK") != "" { if err := exec.Command("eval", "`ssh-agent -k`").Run(); err != nil {