From a2c6be3c194be687058e0b42e81979b0c5ac7bd5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 19 May 2020 14:01:28 +0800 Subject: [PATCH] support Fingerprint (#157) --- main.go | 30 ++++++++++++++------ plugin.go | 39 ++++++++++++++------------ plugin_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 27 deletions(-) diff --git a/main.go b/main.go index 26f5d98..59157fe 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,11 @@ func main() { EnvVar: "PLUGIN_CIPHERS,SSH_CIPHERS,CIPHERS,INPUT_CIPHERS", Value: &defaultCiphers, }, + cli.StringFlag{ + Name: "fingerprint", + Usage: "fingerprint as unpadded base64 encoded sha256 hash.", + EnvVar: "PLUGIN_FINGERPRINT,SSH_FINGERPRINT,FINGERPRINT,INPUT_FINGERPRINT", + }, cli.StringSliceFlag{ Name: "host,H", Usage: "connect to host", @@ -157,6 +162,11 @@ func main() { EnvVar: "PLUGIN_PROXY_CIPHERS,SSH_PROXY_CIPHERS,PROXY_CIPHERS,INPUT_PROXY_CIPHERS", Value: &defaultCiphers, }, + cli.StringFlag{ + Name: "proxy.fingerprint", + Usage: "fingerprint as unpadded base64 encoded sha256 hash.", + EnvVar: "PLUGIN_PROXY_FINGERPRINT,SSH_PROXY_FINGERPRINT,PROXY_FINGERPRINT,INPUT_PROXY_FINGERPRINT", + }, cli.StringSliceFlag{ Name: "envs", Usage: "pass environment variable to shell script", @@ -219,6 +229,7 @@ func run(c *cli.Context) error { Username: c.String("user"), Password: c.String("password"), Passphrase: c.String("ssh-passphrase"), + Fingerprint: c.String("fingerprint"), Host: c.StringSlice("host"), Port: c.Int("port"), Timeout: c.Duration("timeout"), @@ -230,15 +241,16 @@ func run(c *cli.Context) error { Sync: c.Bool("sync"), Ciphers: c.StringSlice("ciphers"), Proxy: easyssh.DefaultConfig{ - Key: c.String("proxy.ssh-key"), - KeyPath: c.String("proxy.key-path"), - User: c.String("proxy.username"), - Password: c.String("proxy.password"), - Passphrase: c.String("proxy.ssh-passphrase"), - 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"), + KeyPath: c.String("proxy.key-path"), + User: c.String("proxy.username"), + Password: c.String("proxy.password"), + Passphrase: c.String("proxy.ssh-passphrase"), + Fingerprint: c.String("proxy.fingerprint"), + Server: c.String("proxy.host"), + Port: c.String("proxy.port"), + Timeout: c.Duration("proxy.timeout"), + Ciphers: c.StringSlice("proxy.ciphers"), }, }, Writer: os.Stdout, diff --git a/plugin.go b/plugin.go index 09186fb..6bfc4bf 100644 --- a/plugin.go +++ b/plugin.go @@ -30,6 +30,7 @@ type ( Password string Host []string Port int + Fingerprint string Timeout time.Duration CommandTimeout time.Duration Script []string @@ -55,25 +56,27 @@ func escapeArg(arg string) string { func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) { // 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: strconv.Itoa(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: strconv.Itoa(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 cd6bd9a..5cf5e84 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "io" + "io/ioutil" "os" "reflect" "strings" @@ -11,6 +12,7 @@ import ( "github.com/appleboy/easyssh-proxy" "github.com/stretchr/testify/assert" + "golang.org/x/crypto/ssh" ) func TestMissingHostOrUser(t *testing.T) { @@ -384,6 +386,80 @@ func TestCommandOutput(t *testing.T) { assert.Equal(t, unindent(expected), unindent(buffer.String())) } +func TestWrongFingerprint(t *testing.T) { + var ( + buffer bytes.Buffer + ) + + plugin := Plugin{ + Config: Config{ + Host: []string{"localhost"}, + Username: "drone-scp", + Port: 22, + KeyPath: "./tests/.ssh/id_rsa", + Script: []string{ + "whoami", + }, + Fingerprint: "wrong", + }, + Writer: &buffer, + } + + err := plugin.Exec() + 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 TestFingerprint(t *testing.T) { + var ( + buffer bytes.Buffer + expected = ` + ======CMD====== + whoami + ======END====== + out: drone-scp + ` + ) + + 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", + Script: []string{ + "whoami", + }, + Fingerprint: ssh.FingerprintSHA256(hostKey), + CommandTimeout: 10 * time.Second, + }, + Writer: &buffer, + } + + err = plugin.Exec() + assert.Nil(t, err) + assert.Equal(t, unindent(expected), unindent(buffer.String())) +} + func TestScriptStop(t *testing.T) { var ( buffer bytes.Buffer