From cddaeaaf8d1c443fa50962c442b39760ed90014a Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Thu, 4 Jan 2018 15:06:32 +0800 Subject: [PATCH 01/15] 0.1.1 readme --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 9441194..988e67f 100644 --- a/README.MD +++ b/README.MD @@ -13,7 +13,7 @@ go build 提供 win64 和 linux64 两个平台的可执行文件 -https://github.com/shanghai-edu/multissh/releases/tag/0.1 +https://github.com/shanghai-edu/multissh/releases/ #### 命令体系 ``` From 3b1a46e4e00f29a5ce6c2e114a64040526b6eb5c Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Fri, 5 Jan 2018 13:28:08 +0800 Subject: [PATCH 02/15] 0.1.2 auto add exit cmd --- cmd1.txt.example | 3 +-- cmd2.txt.example | 3 +-- main.go | 2 +- sshconnect.go | 1 + 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd1.txt.example b/cmd1.txt.example index f61b2a7..f58a975 100644 --- a/cmd1.txt.example +++ b/cmd1.txt.example @@ -1,2 +1 @@ -show clock -exit \ No newline at end of file +show clock \ No newline at end of file diff --git a/cmd2.txt.example b/cmd2.txt.example index 12a5f15..341974a 100644 --- a/cmd2.txt.example +++ b/cmd2.txt.example @@ -1,4 +1,3 @@ date sleep 3 -date -exit \ No newline at end of file +date \ No newline at end of file diff --git a/main.go b/main.go index ffdb520..8390fb6 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( ) const ( - VERSION = "0.1.1" + VERSION = "0.1.2" ) type SSHHost struct { diff --git a/sshconnect.go b/sshconnect.go index 8e0c4c9..1962557 100644 --- a/sshconnect.go +++ b/sshconnect.go @@ -74,6 +74,7 @@ func dossh(username, password, ip string, cmdlist []string, port int, ch chan st } defer session.Close() + cmdlist = append(cmdlist, "exit") // cmd := "ls;date;exit" stdinBuf, _ := session.StdinPipe() //fmt.Fprintf(os.Stdout, "%s", stdinBuf) From c1c790e653a4e6e3effe0a31c48a882aacad7980 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Fri, 5 Jan 2018 13:35:27 +0800 Subject: [PATCH 03/15] 0.1.2 readme --- README.MD | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.MD b/README.MD index 988e67f..74a5c0c 100644 --- a/README.MD +++ b/README.MD @@ -48,7 +48,6 @@ https://github.com/shanghai-edu/multissh/releases/ **cmdfile 示例** ``` show clock -exit ``` **hostfile 示例** ``` @@ -85,7 +84,7 @@ exit ## 用法 #### cmd string & host string ``` -./multissh -cmd "show clock;exit" -hosts "192.168.15.101;192.168.15.102" -u admin -p admin +./multissh -cmd "show clock" -hosts "192.168.15.101;192.168.15.102" -u admin -p admin 192.168.15.101 ssh start sw-1#show clock From 87e207932fcab1f25d843752b25057d3272519f5 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Fri, 19 Jan 2018 17:54:08 +0800 Subject: [PATCH 04/15] version 0.2 --- .gitignore | 3 +- README.MD | 302 ++++++++++++++++++++++++++++++++------------ cmd2.txt.example | 6 +- funcs/ssh_test.go | 79 ++++++++++++ funcs/sshconnect.go | 200 +++++++++++++++++++++++++++++ cfg.go => g/cfg.go | 40 +++++- g/const.go | 9 ++ host.txt.example | 4 +- ip.txt.example | 3 +- main.go | 136 ++++++++++---------- ssh.json.example | 22 ++-- ssh_test.go | 53 -------- sshconnect.go | 97 -------------- 13 files changed, 638 insertions(+), 316 deletions(-) create mode 100644 funcs/ssh_test.go create mode 100644 funcs/sshconnect.go rename cfg.go => g/cfg.go (87%) create mode 100644 g/const.go delete mode 100644 ssh_test.go delete mode 100644 sshconnect.go diff --git a/.gitignore b/.gitignore index 2cc6534..98e9d73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.txt *.json -*.exe \ No newline at end of file +*.exe +*.key \ No newline at end of file diff --git a/README.MD b/README.MD index 74a5c0c..cd7243b 100644 --- a/README.MD +++ b/README.MD @@ -2,6 +2,15 @@ 一个简单的并行 SSH 工具,可以批量的对主机通过 SSH 执行命令组合。 +支持: +- 并发执行 +- 单次执行多条命令 +- ip 地址段自动匹配主机(192.168.0.1-192.168.0.100) +- ssh 用户名/密码认证 +- ssh key 认证 +- json 格式输出 +- 输出到文本,文件名为 host.txt + #### 编译 ``` go get ./... @@ -17,33 +26,39 @@ https://github.com/shanghai-edu/multissh/releases/ #### 命令体系 ``` -./multissh -h - -cmd string - cmds // 需要执行的命令组合,多条命令以 ; 分割 +# ./multissh -h +Usage of ./multissh: + -c string + cfg File Path + -ciphers string + ciphers -cmdfile string - cmdfile path //需要执行的命令组合文件,文件内命令按行分割 + cmdfile path + -cmds string + cmds -hostfile string - hostfile path // 需要执行的主机列表文件,主机列表在文件内按行分割 + hostfile path -hosts string - host address list //需要执行的主机列表,多个主机以 ; 分割 + host address list -ipfile string - hostfile path //需要执行的主机(IP)列表文件,IP可以以地址段的方式逐行写在文本内 - -p string - password // 主机的 SSH 密码 - -port int - ssh port (default 22) //主机的 SSH 端口,默认 22 - -u string - username //主机的 SSH 用户名 - -j string - jsonFile //保存大量主机,包括主机地址,SSH用户名,SSH密码,SSH端口,所需执行的cmd指令文件地址 - -outTxt bool - outTxt (default false) //是否允许把结果保存到文件中,文件名为 ssh 连接的主机名(host 或 ip),true为允许 false为默认值 - -t int - timeLimit (default 30) //单个 ssh 会话的最大时间,超过时间命令未执行完则超时 默认为30s + hostfile path + -j print output in json format + -k string + ssh private key + -l In linux mode,multi command combine with && ,such as date&&cd /opt&&ls (default true) -n int - numLimit (default 20) //最大并发访问量 默认为20 + max execute number (default 20) + -outTxt + write result into txt + -p string + password + -port int + ssh port (default 22) + -t int + max timeout (default 30) + -u string + username -v show version - ``` **cmdfile 示例** ``` @@ -51,12 +66,13 @@ show clock ``` **hostfile 示例** ``` -192.168.15.101 +192.168.31.21 192.168.15.102 ``` **ipfile 示例** ``` -192.168.15.101-192.168.15.102 +192.168.15.101-192.168.15.103 +192.168.31.21-192.168.31.22 ``` **ssh.json 示例** @@ -64,17 +80,19 @@ show clock { "SshHosts": [ { - "Host": "192.168.15.101", - "Port": 22, - "Username": "admin", - "Password": "admin", - "CmdFile": "cmd1.txt.example" - }, + "Host": "192.168.31.51", + "Port": 22, + "Username": "admin", + "Password": "admin", + "cmds":"show clock;show clock" + }, { - "Host": "192.168.83.40", - "Port": 22, - "Username": "root", - "Password": "root", + "Host": "192.168.80.131", + "Port": 22, + "Username": "root", + "Password": "", + "key": "./server.key", + "linuxMode": true, "CmdFile": "cmd2.txt.example" } ] @@ -84,78 +102,206 @@ show clock ## 用法 #### cmd string & host string ``` -./multissh -cmd "show clock" -hosts "192.168.15.101;192.168.15.102" -u admin -p admin +# ./multissh -cmds "show clock" -hosts "192.168.31.21;192.168.15.102" -u admin -p password +2018/01/17 14:01:28 Multissh start +2018/01/17 14:01:31 Multissh finished. Process time 2.867808673s. Number of active ip is 2 +host: 192.168.31.21 +========= Result ========= -192.168.15.101 ssh start -sw-1#show clock -05:26:40.649 UTC Tue Jun 6 2017 -sw-1#exit +****************************************************************************** +* Copyright (c) 2004-2016 Hangzhou H3C Tech. Co., Ltd. All rights reserved. * +* Without the owner's prior written consent, * +* no decompiling or reverse-engineering shall be allowed. * +****************************************************************************** -192.168.15.101 ssh end +show clock +14:01:31 CN Wed 01/17/2018 +Time Zone : CN add 08:00:00 +exit -192.168.15.102 ssh start -sw-2#show clock -05:24:38.708 UTC Tue Jun 6 2017 -sw-2#exit +host: 192.168.15.102 +========= Result ========= + +sw-cisco#show clock +05:50:24.935 UTC Wed Jan 17 2018 +sw-cisco#exit -192.168.15.102 ssh end ``` #### cmdfile & hostfile ``` -./multissh -cmdfile cmd.txt -hostfile host.txt -u admin -p admin +# ./multissh -cmdfile cmd1.txt.example -hostfile host.txt.example -u admin -p password +2018/01/17 14:01:28 Multissh start +2018/01/17 14:01:31 Multissh finished. Process time 2.867808673s. Number of active ip is 2 +host: 192.168.31.21 +========= Result ========= -192.168.15.101 ssh start -sw-1#show clock -05:29:43.269 UTC Tue Jun 6 2017 -sw-1#exit +****************************************************************************** +* Copyright (c) 2004-2016 Hangzhou H3C Tech. Co., Ltd. All rights reserved. * +* Without the owner's prior written consent, * +* no decompiling or reverse-engineering shall be allowed. * +****************************************************************************** -192.168.15.101 ssh end +show clock +14:01:31 CN Wed 01/17/2018 +Time Zone : CN add 08:00:00 +exit -192.168.15.102 ssh start -sw-2#show clock -05:27:41.332 UTC Tue Jun 6 2017 -sw-2#exit +host: 192.168.15.102 +========= Result ========= + +sw-cisco#show clock +05:50:24.935 UTC Wed Jan 17 2018 +sw-cisco#exit -192.168.15.102 ssh end ``` #### ipfile ``` -./multissh -cmdfile cmd.txt -ipfile ip.txt -u admin -p admin +# ./multissh -cmdfile cmd1.txt.example -ipfile ip.txt.example -u admin -p password +2018/01/17 14:25:26 Multissh start +2018/01/17 14:25:29 Multissh finished. Process time 2.847347642s. Number of active ip is 5 +host: 192.168.15.101 +========= Result ========= -192.168.15.101 ssh start -sw-1#show clock -05:29:43.269 UTC Tue Jun 6 2017 -sw-1#exit +sw-cisco-1#show clock +06:17:49.422 UTC Wed Jan 17 2018 +sw-cisco-1#exit -192.168.15.101 ssh end +host: 192.168.15.102 +========= Result ========= +sw-cisco-2#show clock +06:14:22.445 UTC Wed Jan 17 2018 +sw-cisco-2#exit -192.168.15.102 ssh start -sw-2#show clock -05:27:41.332 UTC Tue Jun 6 2017 -sw-2#exit +host: 192.168.15.103 +========= Result ========= +sw-cisco-3#show clock +06:19:14.487 UTC Wed Jan 17 2018 +sw-cisco-3#exit + +host: 192.168.31.21 +========= Result ========= + +****************************************************************************** +* Copyright (c) 2004-2016 Hangzhou H3C Tech. Co., Ltd. All rights reserved. * +* Without the owner's prior written consent, * +* no decompiling or reverse-engineering shall be allowed. * +****************************************************************************** + +show clock +14:25:29 CN Wed 01/17/2018 +Time Zone : CN add 08:00:00 +exit + +host: 192.168.31.22 +========= Result ========= + +sw-cisco-4#show clock +14:25:27.639 beijing Wed Jan 17 2018 +sw-cisco-4#exit +``` +#### ssh key-based Auth and linuxMode +``` +# ./multissh -hosts "192.168.80.131" -cmds "date;cd /opt;ls" -u root -k "server.key" +2018/01/17 14:33:55 Multissh start +2018/01/17 14:33:56 Multissh finished. Process time 960.367764ms. Number of active ip is 1 +host: 192.168.80.131 +========= Result ========= +Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-98-generic x86_64) + + * Documentation: https://help.ubuntu.com + * Management: https://landscape.canonical.com + * Support: https://ubuntu.com/advantage + + System information as of Wed Jan 17 14:33:55 CST 2018 + + System load: 0.0 Processes: 335 + Usage of /: 10.0% of 90.18GB Users logged in: 0 + Memory usage: 2% IP address for eth0: 192.168.80.131 + Swap usage: 0% IP address for docker0: 172.17.0.1 + + Graph this data and manage this system at: + https://landscape.canonical.com/ + +0 个可升级软件包。 +0 个安全更新。 + +New release '17.10' available. +Run 'do-release-upgrade' to upgrade to it. + +You have new mail. +Last login: Wed Jan 17 14:29:39 2018 from 202.120.80.201 +root@ubuntu-docker-node3:~# 201817:33:56 CST +root@ubuntu-docker-node3:~# root@ubuntu-docker-node3:/opt# cisco +composer.json +composer.phar +example-oauth2-server +getting-started-with-mmdb +gitlab +gitlab-ce_8.0.4-ce.1_amd64.deb +oauth2-demo-php +oauth2-server-php +python_test +rsyslog-maxminddb +root@ubuntu-docker-node3:/opt# 注销 + +# ./multissh -hosts "192.168.80.131" -cmds "date;cd /opt;ls" -u root -k "server.key" -l +2018/01/17 14:34:02 Multissh start +2018/01/17 14:34:02 Multissh finished. Process time 842.465643ms. Number of active ip is 1 +host: 192.168.80.131 +========= Result ========= +201817:34:02 CST +cisco +composer.json +composer.phar +example-oauth2-server +getting-started-with-mmdb +gitlab +gitlab-ce_8.0.4-ce.1_amd64.deb +oauth2-demo-php +oauth2-server-php +python_test +rsyslog-maxminddb -192.168.15.102 ssh end ``` #### ssh.json ``` -./multissh -j ssh.json -t 30 -n 20 -outTxt -192.168.15.101 ssh start -sw-1#show clock -05:29:43.269 UTC Tue Jun 6 2017 -sw-1#exit +./multissh -c ssh.json.example +2018/01/17 14:29:38 Multissh start +2018/01/17 14:29:41 Multissh finished. Process time 2.922928532s. Number of active ip is 2 +host: 192.168.31.51 +========= Result ========= -192.168.15.101 ssh end +****************************************************************************** +* Copyright (c) 2004-2016 Hangzhou H3C Tech. Co., Ltd. All rights reserved. * +* Without the owner's prior written consent, * +* no decompiling or reverse-engineering shall be allowed. * +****************************************************************************** -192.168.83.40 ssh start -2017年 06月 09日 星期五 09:33:11 CST -2017年 06月 09日 星期五 09:33:14 CST -192.168.83.40 ssh end +show clock +14:29:41 CN Wed 01/17/2018 +Time Zone : CN add 08:00:00 +show clock +14:29:41 CN Wed 01/17/2018 +Time Zone : CN add 08:00:00 +exit + +host: 192.168.80.131 +========= Result ========= +cisco +composer.json +composer.phar +example-oauth2-server +getting-started-with-mmdb +gitlab +gitlab-ce_8.0.4-ce.1_amd64.deb +oauth2-demo-php +oauth2-server-php +python_test +rsyslog-maxminddb ``` -#### TODO -增加使用证书认证的支持 #### LICENSE Apache License 2.0 \ No newline at end of file diff --git a/cmd2.txt.example b/cmd2.txt.example index 341974a..71c5190 100644 --- a/cmd2.txt.example +++ b/cmd2.txt.example @@ -1,3 +1,3 @@ -date -sleep 3 -date \ No newline at end of file +cd /opt +sleep 2 +ls \ No newline at end of file diff --git a/funcs/ssh_test.go b/funcs/ssh_test.go new file mode 100644 index 0000000..a868590 --- /dev/null +++ b/funcs/ssh_test.go @@ -0,0 +1,79 @@ +package funcs + +import ( + "bytes" + // "os" + //"strings" + "testing" +) + +const ( + username = "root" + password = "" + ip = "192.168.80.131" + port = 22 + cmd = "cd /opt;pwd;exit" + key = "../server.key" +) + +/* +func Test_SSH(t *testing.T) { + var cipherList []string + session, err := connect(username, password, ip, key, port, cipherList) + if err != nil { + t.Error(err) + return + } + defer session.Close() + + cmdlist := strings.Split(cmd, ";") + stdinBuf, err := session.StdinPipe() + if err != nil { + t.Error(err) + return + } + + var outbt, errbt bytes.Buffer + session.Stdout = &outbt + + session.Stderr = &errbt + err = session.Shell() + if err != nil { + t.Error(err) + return + } + for _, c := range cmdlist { + c = c + "\n" + stdinBuf.Write([]byte(c)) + + } + session.Wait() + t.Log((outbt.String() + errbt.String())) + return +} +*/ + +func Test_SSH_run(t *testing.T) { + var cipherList []string + session, err := connect(username, password, ip, key, port, cipherList) + if err != nil { + t.Error(err) + return + } + defer session.Close() + + //cmdlist := strings.Split(cmd, ";") + //newcmd := strings.Join(cmdlist, "&&") + var outbt, errbt bytes.Buffer + session.Stdout = &outbt + + session.Stderr = &errbt + err = session.Run(cmd) + if err != nil { + t.Error(err) + return + } + t.Log((outbt.String() + errbt.String())) + + return +} diff --git a/funcs/sshconnect.go b/funcs/sshconnect.go new file mode 100644 index 0000000..afb985f --- /dev/null +++ b/funcs/sshconnect.go @@ -0,0 +1,200 @@ +package funcs + +import ( + "bytes" + "fmt" + "io/ioutil" + + "net" + "strconv" + "strings" + "time" + + "github.com/shanghai-edu/multissh/g" + "golang.org/x/crypto/ssh" +) + +func connect(user, password, host, key string, port int, cipherList []string) (*ssh.Session, error) { + var ( + auth []ssh.AuthMethod + addr string + clientConfig *ssh.ClientConfig + client *ssh.Client + config ssh.Config + session *ssh.Session + err error + ) + // get auth method + auth = make([]ssh.AuthMethod, 0) + if key == "" { + auth = append(auth, ssh.Password(password)) + } else { + pemBytes, err := ioutil.ReadFile(key) + if err != nil { + return nil, err + } + + var signer ssh.Signer + if password == "" { + signer, err = ssh.ParsePrivateKey(pemBytes) + } else { + signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(password)) + } + if err != nil { + return nil, err + } + auth = append(auth, ssh.PublicKeys(signer)) + } + + if len(cipherList) == 0 { + config = ssh.Config{ + Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"}, + } + } else { + config = ssh.Config{ + Ciphers: cipherList, + } + } + + clientConfig = &ssh.ClientConfig{ + User: user, + Auth: auth, + Timeout: 30 * time.Second, + Config: config, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + } + + // connet to ssh + addr = fmt.Sprintf("%s:%d", host, port) + + if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil { + return nil, err + } + + // create session + if session, err = client.NewSession(); err != nil { + return nil, err + } + + modes := ssh.TerminalModes{ + ssh.ECHO: 0, // disable echoing + ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud + ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud + } + + if err := session.RequestPty("xterm", 80, 40, modes); err != nil { + return nil, err + } + + return session, nil +} + +func Dossh(username, password, host, key string, cmdlist []string, port, timeout int, cipherList []string, linuxMode bool, ch chan g.SSHResult) { + chSSH := make(chan g.SSHResult) + if linuxMode { + go dossh_run(username, password, host, key, cmdlist, port, cipherList, chSSH) + } else { + go dossh_session(username, password, host, key, cmdlist, port, cipherList, chSSH) + } + var res g.SSHResult + + select { + case <-time.After(time.Duration(timeout) * time.Second): + res.Host = host + res.Success = false + res.Result = ("SSH run timeout:" + strconv.Itoa(timeout) + " second.") + ch <- res + case res = <-chSSH: + ch <- res + } + return +} + +func dossh_session(username, password, host, key string, cmdlist []string, port int, cipherList []string, ch chan g.SSHResult) { + session, err := connect(username, password, host, key, port, cipherList) + var sshResult g.SSHResult + sshResult.Host = host + + if err != nil { + sshResult.Success = false + sshResult.Result = fmt.Sprintf("<%s>", err.Error()) + ch <- sshResult + return + } + defer session.Close() + + cmdlist = append(cmdlist, "exit") + + stdinBuf, _ := session.StdinPipe() + + var outbt, errbt bytes.Buffer + session.Stdout = &outbt + + session.Stderr = &errbt + err = session.Shell() + if err != nil { + sshResult.Success = false + sshResult.Result = fmt.Sprintf("<%s>", err.Error()) + ch <- sshResult + return + } + for _, c := range cmdlist { + c = c + "\n" + stdinBuf.Write([]byte(c)) + } + session.Wait() + if errbt.String() != "" { + sshResult.Success = false + sshResult.Result = errbt.String() + ch <- sshResult + } else { + sshResult.Success = true + sshResult.Result = outbt.String() + ch <- sshResult + } + + return +} + +func dossh_run(username, password, host, key string, cmdlist []string, port int, cipherList []string, ch chan g.SSHResult) { + session, err := connect(username, password, host, key, port, cipherList) + var sshResult g.SSHResult + sshResult.Host = host + + if err != nil { + sshResult.Success = false + sshResult.Result = fmt.Sprintf("<%s>", err.Error()) + ch <- sshResult + return + } + defer session.Close() + + cmdlist = append(cmdlist, "exit") + newcmd := strings.Join(cmdlist, "&&") + + var outbt, errbt bytes.Buffer + session.Stdout = &outbt + + session.Stderr = &errbt + err = session.Run(newcmd) + if err != nil { + sshResult.Success = false + sshResult.Result = fmt.Sprintf("<%s>", err.Error()) + ch <- sshResult + return + } + + if errbt.String() != "" { + sshResult.Success = false + sshResult.Result = errbt.String() + ch <- sshResult + } else { + sshResult.Success = true + sshResult.Result = outbt.String() + ch <- sshResult + } + + return +} diff --git a/cfg.go b/g/cfg.go similarity index 87% rename from cfg.go rename to g/cfg.go index 7aa4d88..2a9ed96 100644 --- a/cfg.go +++ b/g/cfg.go @@ -1,4 +1,4 @@ -package main +package g import ( "bufio" @@ -12,6 +12,38 @@ import ( "strings" ) +type SSHHost struct { + Host string + Port int + Username string + Password string + CmdFile string + Cmds string + CmdList []string + Key string + LinuxMode bool + Result SSHResult +} + +type HostJson struct { + SshHosts []SSHHost +} + +type SSHResult struct { + Host string + Success bool + Result string +} + +func SplitString(str string) (strList []string) { + if strings.Contains(str, ",") { + strList = strings.Split(str, ",") + } else { + strList = strings.Split(str, ";") + } + return +} + func GetfileAll(filePath string) ([]byte, error) { result, err := ioutil.ReadFile(filePath) if err != nil { @@ -52,8 +84,8 @@ func GetJsonFile(filePath string) ([]SSHHost, error) { result = m.SshHosts return result, nil } -func WriteIntoTxt(sshHost SSHHost) error { - outputFile, outputError := os.OpenFile(sshHost.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666) +func WriteIntoTxt(sshResult SSHResult) error { + outputFile, outputError := os.OpenFile(sshResult.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666) if outputError != nil { return outputError } @@ -62,7 +94,7 @@ func WriteIntoTxt(sshHost SSHHost) error { outputWriter := bufio.NewWriter(outputFile) //var outputString string - outputString := sshHost.Result + outputString := sshResult.Result outputWriter.WriteString(outputString) outputWriter.Flush() return nil diff --git a/g/const.go b/g/const.go new file mode 100644 index 0000000..ecbb867 --- /dev/null +++ b/g/const.go @@ -0,0 +1,9 @@ +package g + +// changelog: +// 0.1 fisrt version +// 0.1.2 fix ssh error on h3c switch +// 0.2 +const ( + VERSION = "0.2" +) diff --git a/host.txt.example b/host.txt.example index 024af64..097f8b4 100644 --- a/host.txt.example +++ b/host.txt.example @@ -1,2 +1,2 @@ -192.168.15.101 -192.168.15.110 \ No newline at end of file +192.168.31.21 +192.168.15.102 \ No newline at end of file diff --git a/ip.txt.example b/ip.txt.example index a99ff44..746458e 100644 --- a/ip.txt.example +++ b/ip.txt.example @@ -1 +1,2 @@ -192.168.15.101-192.168.15.110 \ No newline at end of file +192.168.15.101-192.168.15.110 +192.168.31.21-192.168.31.22 \ No newline at end of file diff --git a/main.go b/main.go index 8390fb6..4a16c95 100644 --- a/main.go +++ b/main.go @@ -1,65 +1,52 @@ package main import ( + "encoding/json" "flag" "fmt" "log" "os" - "strconv" - "strings" + "time" - // "github.com/bitly/go-simplejson" -) -const ( - VERSION = "0.1.2" + "github.com/shanghai-edu/multissh/funcs" + "github.com/shanghai-edu/multissh/g" ) -type SSHHost struct { - Host string - Port int - Username string - Password string - CmdFile string - Cmd []string - Result string -} -type HostJson struct { - SshHosts []SSHHost -} - func main() { version := flag.Bool("v", false, "show version") hosts := flag.String("hosts", "", "host address list") - cmd := flag.String("cmd", "", "cmds") + cmds := flag.String("cmds", "", "cmds") username := flag.String("u", "", "username") password := flag.String("p", "", "password") + key := flag.String("k", "", "ssh private key") port := flag.Int("port", 22, "ssh port") + ciphers := flag.String("ciphers", "", "ciphers") cmdFile := flag.String("cmdfile", "", "cmdfile path") hostFile := flag.String("hostfile", "", "hostfile path") ipFile := flag.String("ipfile", "", "hostfile path") - //gu - jsonFile := flag.String("j", "", "Json File Path") + cfgFile := flag.String("c", "", "cfg File Path") + jsonMode := flag.Bool("j", false, "print output in json format") outTxt := flag.Bool("outTxt", false, "write result into txt") + linuxMode := flag.Bool("l", false, "In linux mode,multi command combine with && ,such as date&&cd /opt&&ls") timeLimit := flag.Int("t", 30, "max timeout") numLimit := flag.Int("n", 20, "max execute number") flag.Parse() - var cmdList []string - var hostList []string + + var cmdList, hostList, cipherList []string var err error - sshHosts := []SSHHost{} - var host_Struct SSHHost - timeout := time.Duration(*timeLimit) * time.Second + sshHosts := []g.SSHHost{} + var host_Struct g.SSHHost if *version { - fmt.Println(VERSION) + fmt.Println(g.VERSION) os.Exit(0) } if *ipFile != "" { - hostList, err = GetIpList(*ipFile) + hostList, err = g.GetIpList(*ipFile) if err != nil { log.Println("load hostlist error: ", err) return @@ -67,89 +54,104 @@ func main() { } if *hostFile != "" { - hostList, err = Getfile(*hostFile) + hostList, err = g.Getfile(*hostFile) if err != nil { log.Println("load hostfile error: ", err) return } } if *hosts != "" { - hostList = strings.Split(*hosts, ";") + hostList = g.SplitString(*hosts) } if *cmdFile != "" { - cmdList, err = Getfile(*cmdFile) + cmdList, err = g.Getfile(*cmdFile) if err != nil { log.Println("load cmdfile error: ", err) return } } - if *cmd != "" { - cmdList = strings.Split(*cmd, ";") + if *cmds != "" { + cmdList = g.SplitString(*cmds) + } - if *jsonFile == "" { + if *ciphers != "" { + cipherList = g.SplitString(*ciphers) + } + if *cfgFile == "" { for _, host := range hostList { host_Struct.Host = host host_Struct.Username = *username host_Struct.Password = *password host_Struct.Port = *port - host_Struct.Cmd = cmdList + host_Struct.CmdList = cmdList + host_Struct.Key = *key + host_Struct.LinuxMode = *linuxMode sshHosts = append(sshHosts, host_Struct) } - } - //gu - if *jsonFile != "" { - sshHosts, err = GetJsonFile(*jsonFile) + } else { + sshHosts, err = g.GetJsonFile(*cfgFile) if err != nil { - log.Println("load jsonFile error: ", err) + log.Println("load cfgFile error: ", err) return } for i := 0; i < len(sshHosts); i++ { - cmdList, err = Getfile(sshHosts[i].CmdFile) - if err != nil { - log.Println("load cmdFile error: ", err) - return + if sshHosts[i].Cmds != "" { + sshHosts[i].CmdList = g.SplitString(sshHosts[i].Cmds) + } else { + cmdList, err = g.Getfile(sshHosts[i].CmdFile) + if err != nil { + log.Println("load cmdFile error: ", err) + return + } + sshHosts[i].CmdList = cmdList } - sshHosts[i].Cmd = cmdList } } chLimit := make(chan bool, *numLimit) //控制并发访问量 - chs := make([]chan string, len(sshHosts)) - limitFunc := func(chLimit chan bool, ch chan string, host SSHHost) { - dossh(host.Username, host.Password, host.Host, host.Cmd, host.Port, ch) + chs := make([]chan g.SSHResult, len(sshHosts)) + startTime := time.Now() + log.Println("Multissh start") + limitFunc := func(chLimit chan bool, ch chan g.SSHResult, host g.SSHHost) { + funcs.Dossh(host.Username, host.Password, host.Host, host.Key, host.CmdList, host.Port, *timeLimit, cipherList, host.LinuxMode, ch) <-chLimit } for i, host := range sshHosts { - chs[i] = make(chan string, 1) + chs[i] = make(chan g.SSHResult, 1) chLimit <- true go limitFunc(chLimit, chs[i], host) } - for i, ch := range chs { - fmt.Println(sshHosts[i].Host, " ssh start") - select { - case res := <-ch: - if res != "" { - fmt.Println(res) - sshHosts[i].Result += res - } - case <-time.After(timeout): - log.Println("SSH run timeout") - sshHosts[i].Result += ("SSH run timeout:" + strconv.Itoa(int(*timeLimit)) + "second.") + sshResults := []g.SSHResult{} + for _, ch := range chs { + res := <-ch + if res.Result != "" { + sshResults = append(sshResults, res) } - - fmt.Println(sshHosts[i].Host, " ssh end") } - + endTime := time.Now() + log.Printf("Multissh finished. Process time %s. Number of active ip is %d", endTime.Sub(startTime), len(sshHosts)) //gu if *outTxt { - for i := 0; i < len(sshHosts); i++ { - err = WriteIntoTxt(sshHosts[i]) + for _, sshResult := range sshResults { + err = g.WriteIntoTxt(sshResult) if err != nil { log.Println("write into txt error: ", err) return } } } - + if *jsonMode { + jsonResult, err := json.Marshal(sshResults) + if err != nil { + log.Println("json Marshal error: ", err) + } + fmt.Println(string(jsonResult)) + } else { + for _, sshResult := range sshResults { + fmt.Println("host: ", sshResult.Host) + fmt.Println("========= Result =========") + fmt.Println(sshResult.Result) + } + } } diff --git a/ssh.json.example b/ssh.json.example index f18a812..bede1a5 100644 --- a/ssh.json.example +++ b/ssh.json.example @@ -1,17 +1,19 @@ { "SshHosts": [ { - "Host": "192.168.15.101", - "Port": 22, - "Username": "admin", - "Password": "admin", - "CmdFile": "cmd1.txt.example" - }, + "Host": "192.168.31.51", + "Port": 22, + "Username": "admin", + "Password": "admin", + "cmds":"show clock;show clock" + }, { - "Host": "192.168.83.40", - "Port": 22, - "Username": "root", - "Password": "root", + "Host": "192.168.80.131", + "Port": 22, + "Username": "root", + "Password": "", + "key": "./server.key", + "linuxMode": true, "CmdFile": "cmd2.txt.example" } ] diff --git a/ssh_test.go b/ssh_test.go deleted file mode 100644 index 93c615c..0000000 --- a/ssh_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "os" - "strings" - - "testing" -) - -const ( - username = "admin" - password = "admin" - ip = "192.168.31.21" - port = 22 - cmd = "show clock;exit" -) - -func Test_SSH(t *testing.T) { - session, err := connect(username, password, ip, port) - if err != nil { - t.Error(err) - return - } - defer session.Close() - - cmdlist := strings.Split(cmd, ";") - - stdinBuf, err := session.StdinPipe() - if err != nil { - t.Error(err) - return - } - // var bt bytes.Buffer - // session.Stdout = &bt - t.Log(session.Stdout) - t.Log(session.Stderr) - session.Stdout = os.Stdout - session.Stderr = os.Stderr - session.Stdin = os.Stdin - err = session.Shell() - if err != nil { - t.Error(err) - return - } - for _, c := range cmdlist { - c = c + "\n" - stdinBuf.Write([]byte(c)) - } - session.Wait() - t.Error(err) - // t.Log(bt.String()) - return -} diff --git a/sshconnect.go b/sshconnect.go deleted file mode 100644 index 1962557..0000000 --- a/sshconnect.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "net" - //"os" - - "time" - - "golang.org/x/crypto/ssh" -) - -func connect(user, password, host string, port int) (*ssh.Session, error) { - var ( - auth []ssh.AuthMethod - addr string - clientConfig *ssh.ClientConfig - client *ssh.Client - config ssh.Config - session *ssh.Session - err error - ) - // get auth method - auth = make([]ssh.AuthMethod, 0) - auth = append(auth, ssh.Password(password)) - - config = ssh.Config{ - Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour"}, - } - - clientConfig = &ssh.ClientConfig{ - User: user, - Auth: auth, - Timeout: 30 * time.Second, - Config: config, - HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { - return nil - }, - } - - // connet to ssh - addr = fmt.Sprintf("%s:%d", host, port) - - if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil { - return nil, err - } - - // create session - if session, err = client.NewSession(); err != nil { - return nil, err - } - modes := ssh.TerminalModes{ - ssh.ECHO: 0, // disable echoing - ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud - ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud - } - - if err := session.RequestPty("xterm", 80, 40, modes); err != nil { - return nil, err - } - - return session, nil -} - -func dossh(username, password, ip string, cmdlist []string, port int, ch chan string) { - session, err := connect(username, password, ip, port) - - if err != nil { - ch <- fmt.Sprintf("<%s>", err.Error()) - //<-chLimit - return - - } - defer session.Close() - - cmdlist = append(cmdlist, "exit") - // cmd := "ls;date;exit" - stdinBuf, _ := session.StdinPipe() - //fmt.Fprintf(os.Stdout, "%s", stdinBuf) - var outbt, errbt bytes.Buffer - session.Stdout = &outbt - - session.Stderr = &errbt - err = session.Shell() - for _, c := range cmdlist { - c = c + "\n" - stdinBuf.Write([]byte(c)) - - } - session.Wait() - - ch <- (outbt.String() + errbt.String()) - //<-chLimit - return - -} From 378c12a285465dfacff0aaca22fc28d4ff8cc5c0 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Sat, 20 Jan 2018 10:53:42 +0800 Subject: [PATCH 05/15] 0.2.1-version --- g/cfg.go | 16 +++++++++++++++- g/const.go | 3 ++- main.go | 15 ++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/g/cfg.go b/g/cfg.go index 2a9ed96..0c98fc1 100644 --- a/g/cfg.go +++ b/g/cfg.go @@ -100,7 +100,21 @@ func WriteIntoTxt(sshResult SSHResult) error { return nil } -func GetIpList(filePath string) ([]string, error) { +func GetIpList(ipString string) ([]string, error) { + res := SplitString(ipString) + var allIp []string + if len(res) > 0 { + for _, sip := range res { + aip := ParseIp(sip) + for _, ip := range aip { + allIp = append(allIp, ip) + } + } + } + return allIp, nil +} + +func GetIpListFromFile(filePath string) ([]string, error) { res, err := Getfile(filePath) if err != nil { return nil, nil diff --git a/g/const.go b/g/const.go index ecbb867..71090c5 100644 --- a/g/const.go +++ b/g/const.go @@ -4,6 +4,7 @@ package g // 0.1 fisrt version // 0.1.2 fix ssh error on h3c switch // 0.2 +// 0.2.1 const ( - VERSION = "0.2" + VERSION = "0.2.1" ) diff --git a/main.go b/main.go index 4a16c95..50982ec 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( func main() { version := flag.Bool("v", false, "show version") hosts := flag.String("hosts", "", "host address list") + ips := flag.String("ips", "", "ip address list") cmds := flag.String("cmds", "", "cmds") username := flag.String("u", "", "username") password := flag.String("p", "", "password") @@ -24,7 +25,7 @@ func main() { ciphers := flag.String("ciphers", "", "ciphers") cmdFile := flag.String("cmdfile", "", "cmdfile path") hostFile := flag.String("hostfile", "", "hostfile path") - ipFile := flag.String("ipfile", "", "hostfile path") + ipFile := flag.String("ipfile", "", "ipfile path") cfgFile := flag.String("c", "", "cfg File Path") jsonMode := flag.Bool("j", false, "print output in json format") outTxt := flag.Bool("outTxt", false, "write result into txt") @@ -46,9 +47,9 @@ func main() { } if *ipFile != "" { - hostList, err = g.GetIpList(*ipFile) + hostList, err = g.GetIpListFromFile(*ipFile) if err != nil { - log.Println("load hostlist error: ", err) + log.Println("load iplist error: ", err) return } } @@ -60,6 +61,14 @@ func main() { return } } + if *ips != "" { + hostList, err = g.GetIpList(*ips) + if err != nil { + log.Println("load iplist error: ", err) + return + } + } + if *hosts != "" { hostList = g.SplitString(*hosts) } From 360caeb82e42b7d6f80b7c0a47fd1f11c5857e18 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Sat, 20 Jan 2018 11:02:05 +0800 Subject: [PATCH 06/15] README --- README.MD | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.MD b/README.MD index cd7243b..fb7afc4 100644 --- a/README.MD +++ b/README.MD @@ -41,11 +41,13 @@ Usage of ./multissh: -hosts string host address list -ipfile string - hostfile path + ipfile path + -ips string + ip address list -j print output in json format -k string ssh private key - -l In linux mode,multi command combine with && ,such as date&&cd /opt&&ls (default true) + -l In linux mode,multi command combine with && ,such as date&&cd /opt&&ls -n int max execute number (default 20) -outTxt From 82bd4c657713ec6de36773205d7926f06fe1f5bd Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Mon, 9 Apr 2018 10:06:45 +0800 Subject: [PATCH 07/15] version 0.2.2 add filelocate --- g/cfg.go | 4 ++-- g/const.go | 3 ++- main.go | 17 ++++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/g/cfg.go b/g/cfg.go index 0c98fc1..815cb8a 100644 --- a/g/cfg.go +++ b/g/cfg.go @@ -84,8 +84,8 @@ func GetJsonFile(filePath string) ([]SSHHost, error) { result = m.SshHosts return result, nil } -func WriteIntoTxt(sshResult SSHResult) error { - outputFile, outputError := os.OpenFile(sshResult.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666) +func WriteIntoTxt(sshResult SSHResult, locate string) error { + outputFile, outputError := os.OpenFile(locate+sshResult.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666) if outputError != nil { return outputError } diff --git a/g/const.go b/g/const.go index 71090c5..6f88939 100644 --- a/g/const.go +++ b/g/const.go @@ -5,6 +5,7 @@ package g // 0.1.2 fix ssh error on h3c switch // 0.2 // 0.2.1 +// add write locate file const ( - VERSION = "0.2.1" + VERSION = "0.2.2" ) diff --git a/main.go b/main.go index 50982ec..439e461 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ func main() { cfgFile := flag.String("c", "", "cfg File Path") jsonMode := flag.Bool("j", false, "print output in json format") outTxt := flag.Bool("outTxt", false, "write result into txt") + fileLocate := flag.String("f", "", "write file locate") linuxMode := flag.Bool("l", false, "In linux mode,multi command combine with && ,such as date&&cd /opt&&ls") timeLimit := flag.Int("t", 30, "max timeout") numLimit := flag.Int("n", 20, "max execute number") @@ -143,12 +144,13 @@ func main() { //gu if *outTxt { for _, sshResult := range sshResults { - err = g.WriteIntoTxt(sshResult) + err = g.WriteIntoTxt(sshResult, *fileLocate) if err != nil { log.Println("write into txt error: ", err) return } } + return } if *jsonMode { jsonResult, err := json.Marshal(sshResults) @@ -156,11 +158,12 @@ func main() { log.Println("json Marshal error: ", err) } fmt.Println(string(jsonResult)) - } else { - for _, sshResult := range sshResults { - fmt.Println("host: ", sshResult.Host) - fmt.Println("========= Result =========") - fmt.Println(sshResult.Result) - } + return } + for _, sshResult := range sshResults { + fmt.Println("host: ", sshResult.Host) + fmt.Println("========= Result =========") + fmt.Println(sshResult.Result) + } + } From de1b1f2b59b0dff7fa746acc9261b6cdc153a072 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Mon, 9 Apr 2018 10:08:13 +0800 Subject: [PATCH 08/15] readme --- README.MD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.MD b/README.MD index fb7afc4..0d96985 100644 --- a/README.MD +++ b/README.MD @@ -36,6 +36,8 @@ Usage of ./multissh: cmdfile path -cmds string cmds + -f string + write file locate -hostfile string hostfile path -hosts string From e704825aa95b73ec43688a4aacc96e9166113057 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Tue, 3 Jul 2018 13:55:49 +0800 Subject: [PATCH 09/15] 0.2.3 --- .gitignore | 3 ++- LICENSE | 2 +- funcs/ssh_test.go | 6 +++--- g/cfg.go | 6 +++++- g/const.go | 4 +++- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 98e9d73..e722b28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.txt *.json *.exe -*.key \ No newline at end of file +*.key +*.zip \ No newline at end of file diff --git a/LICENSE b/LICENSE index e8e87ec..535a350 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2017] [shanghai-edu] + Copyright [2017] [shanghai-edu & ECNU] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/funcs/ssh_test.go b/funcs/ssh_test.go index a868590..0ff1d7e 100644 --- a/funcs/ssh_test.go +++ b/funcs/ssh_test.go @@ -3,7 +3,7 @@ package funcs import ( "bytes" // "os" - //"strings" + "strings" "testing" ) @@ -16,7 +16,6 @@ const ( key = "../server.key" ) -/* func Test_SSH(t *testing.T) { var cipherList []string session, err := connect(username, password, ip, key, port, cipherList) @@ -51,8 +50,8 @@ func Test_SSH(t *testing.T) { t.Log((outbt.String() + errbt.String())) return } -*/ +/* func Test_SSH_run(t *testing.T) { var cipherList []string session, err := connect(username, password, ip, key, port, cipherList) @@ -77,3 +76,4 @@ func Test_SSH_run(t *testing.T) { return } +*/ diff --git a/g/cfg.go b/g/cfg.go index 815cb8a..b3a3a32 100644 --- a/g/cfg.go +++ b/g/cfg.go @@ -80,7 +80,11 @@ func GetJsonFile(filePath string) ([]SSHHost, error) { return result, err } var m HostJson - json.Unmarshal(b, &m) + err = json.Unmarshal(b, &m) + if err != nil { + log.Println("read file ", filePath, err) + return result, err + } result = m.SshHosts return result, nil } diff --git a/g/const.go b/g/const.go index 6f88939..f4256f1 100644 --- a/g/const.go +++ b/g/const.go @@ -6,6 +6,8 @@ package g // 0.2 // 0.2.1 // add write locate file +// json Unmarshal with error +// 0.2.3 const ( - VERSION = "0.2.2" + VERSION = "0.2.3" ) From c7a73670a63008034f4d8cfc57a7fd9ec47f479a Mon Sep 17 00:00:00 2001 From: Dravening <282864784@qq.com> Date: Tue, 11 Aug 2020 16:29:23 +0800 Subject: [PATCH 10/15] fix:Adapt to some certain types of devices(switch). --- funcs/sshconnect.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/funcs/sshconnect.go b/funcs/sshconnect.go index afb985f..403c802 100644 --- a/funcs/sshconnect.go +++ b/funcs/sshconnect.go @@ -48,7 +48,8 @@ func connect(user, password, host, key string, port int, cipherList []string) (* if len(cipherList) == 0 { config = ssh.Config{ - Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"}, + Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"}, + KeyExchanges: []string{"diffie-hellman-group-exchange-sha1", "diffie-hellman-group1-sha1", "diffie-hellman-group-exchange-sha256"}, } } else { config = ssh.Config{ From f369e796bfe767ee20acb7fe49f298ba91f118f9 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Sat, 9 Oct 2021 00:03:45 +0800 Subject: [PATCH 11/15] VERSION 0.3.0 --- g/const.go | 2 +- go.mod | 7 +++++++ go.sum | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/g/const.go b/g/const.go index f4256f1..43aee6a 100644 --- a/g/const.go +++ b/g/const.go @@ -9,5 +9,5 @@ package g // json Unmarshal with error // 0.2.3 const ( - VERSION = "0.2.3" + VERSION = "0.3.0" ) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6dbc2b4 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/shanghai-edu/multissh + +go 1.17 + +require golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + +require golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9bd8c9b --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 770df7122ead1e290a75f88b3ff88794f4cfc434 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Fri, 11 Feb 2022 15:26:40 +0800 Subject: [PATCH 12/15] VERSION 0.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持在 sshHost 中配置 ciphers 和 keyExchanges 参数,以更好的适配不同的服务器环境 --- funcs/sshconnect.go | 32 ++++++++++++++++---------------- g/cfg.go | 17 ++++++++++++----- g/const.go | 2 +- main.go | 13 ++++++++++--- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/funcs/sshconnect.go b/funcs/sshconnect.go index 403c802..fd8f359 100644 --- a/funcs/sshconnect.go +++ b/funcs/sshconnect.go @@ -14,7 +14,7 @@ import ( "golang.org/x/crypto/ssh" ) -func connect(user, password, host, key string, port int, cipherList []string) (*ssh.Session, error) { +func connect(user, password, host, key string, port int, cipherList, keyExchangeList []string) (*ssh.Session, error) { var ( auth []ssh.AuthMethod addr string @@ -45,16 +45,16 @@ func connect(user, password, host, key string, port int, cipherList []string) (* } auth = append(auth, ssh.PublicKeys(signer)) } - if len(cipherList) == 0 { - config = ssh.Config{ - Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"}, - KeyExchanges: []string{"diffie-hellman-group-exchange-sha1", "diffie-hellman-group1-sha1", "diffie-hellman-group-exchange-sha256"}, - } + config.Ciphers = []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"} } else { - config = ssh.Config{ - Ciphers: cipherList, - } + config.Ciphers = cipherList + } + + if len(keyExchangeList) == 0 { + config.KeyExchanges = []string{"diffie-hellman-group-exchange-sha1", "diffie-hellman-group1-sha1", "diffie-hellman-group-exchange-sha256"} + } else { + config.KeyExchanges = keyExchangeList } clientConfig = &ssh.ClientConfig{ @@ -92,12 +92,12 @@ func connect(user, password, host, key string, port int, cipherList []string) (* return session, nil } -func Dossh(username, password, host, key string, cmdlist []string, port, timeout int, cipherList []string, linuxMode bool, ch chan g.SSHResult) { +func Dossh(username, password, host, key string, cmdlist []string, port, timeout int, cipherList, keyExchangeList []string, linuxMode bool, ch chan g.SSHResult) { chSSH := make(chan g.SSHResult) if linuxMode { - go dossh_run(username, password, host, key, cmdlist, port, cipherList, chSSH) + go dossh_run(username, password, host, key, cmdlist, port, cipherList, keyExchangeList, chSSH) } else { - go dossh_session(username, password, host, key, cmdlist, port, cipherList, chSSH) + go dossh_session(username, password, host, key, cmdlist, port, cipherList, keyExchangeList, chSSH) } var res g.SSHResult @@ -113,8 +113,8 @@ func Dossh(username, password, host, key string, cmdlist []string, port, timeout return } -func dossh_session(username, password, host, key string, cmdlist []string, port int, cipherList []string, ch chan g.SSHResult) { - session, err := connect(username, password, host, key, port, cipherList) +func dossh_session(username, password, host, key string, cmdlist []string, port int, cipherList, keyExchangeList []string, ch chan g.SSHResult) { + session, err := connect(username, password, host, key, port, cipherList, keyExchangeList) var sshResult g.SSHResult sshResult.Host = host @@ -159,8 +159,8 @@ func dossh_session(username, password, host, key string, cmdlist []string, port return } -func dossh_run(username, password, host, key string, cmdlist []string, port int, cipherList []string, ch chan g.SSHResult) { - session, err := connect(username, password, host, key, port, cipherList) +func dossh_run(username, password, host, key string, cmdlist []string, port int, cipherList, keyExchangeList []string, ch chan g.SSHResult) { + session, err := connect(username, password, host, key, port, cipherList, keyExchangeList) var sshResult g.SSHResult sshResult.Host = host diff --git a/g/cfg.go b/g/cfg.go index b3a3a32..cfe81be 100644 --- a/g/cfg.go +++ b/g/cfg.go @@ -27,6 +27,12 @@ type SSHHost struct { type HostJson struct { SshHosts []SSHHost + Global GlobalConfig +} + +type GlobalConfig struct { + Ciphers string + KeyExchanges string } type SSHResult struct { @@ -36,6 +42,9 @@ type SSHResult struct { } func SplitString(str string) (strList []string) { + if str == "" { + return + } if strings.Contains(str, ",") { strList = strings.Split(str, ",") } else { @@ -72,20 +81,18 @@ func Getfile(filePath string) ([]string, error) { } //gu -func GetJsonFile(filePath string) ([]SSHHost, error) { - result := []SSHHost{} +func GetJsonFile(filePath string) (HostJson, error) { + var result HostJson b, err := ioutil.ReadFile(filePath) if err != nil { log.Println("read file ", filePath, err) return result, err } - var m HostJson - err = json.Unmarshal(b, &m) + err = json.Unmarshal(b, &result) if err != nil { log.Println("read file ", filePath, err) return result, err } - result = m.SshHosts return result, nil } func WriteIntoTxt(sshResult SSHResult, locate string) error { diff --git a/g/const.go b/g/const.go index 43aee6a..9e9db32 100644 --- a/g/const.go +++ b/g/const.go @@ -9,5 +9,5 @@ package g // json Unmarshal with error // 0.2.3 const ( - VERSION = "0.3.0" + VERSION = "0.4.0" ) diff --git a/main.go b/main.go index 439e461..7b7693f 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ func main() { key := flag.String("k", "", "ssh private key") port := flag.Int("port", 22, "ssh port") ciphers := flag.String("ciphers", "", "ciphers") + keyExchanges := flag.String("keyexchanges", "", "keyexchanges") cmdFile := flag.String("cmdfile", "", "cmdfile path") hostFile := flag.String("hostfile", "", "hostfile path") ipFile := flag.String("ipfile", "", "ipfile path") @@ -36,7 +37,7 @@ func main() { flag.Parse() - var cmdList, hostList, cipherList []string + var cmdList, hostList, cipherList, keyExchangeList []string var err error sshHosts := []g.SSHHost{} @@ -88,6 +89,9 @@ func main() { if *ciphers != "" { cipherList = g.SplitString(*ciphers) } + if *keyExchanges != "" { + keyExchangeList = g.SplitString(*keyExchanges) + } if *cfgFile == "" { for _, host := range hostList { host_Struct.Host = host @@ -100,11 +104,14 @@ func main() { sshHosts = append(sshHosts, host_Struct) } } else { - sshHosts, err = g.GetJsonFile(*cfgFile) + sshHostConfig, err := g.GetJsonFile(*cfgFile) if err != nil { log.Println("load cfgFile error: ", err) return } + cipherList = g.SplitString(sshHostConfig.Global.Ciphers) + keyExchangeList = g.SplitString(sshHostConfig.Global.KeyExchanges) + sshHosts = sshHostConfig.SshHosts for i := 0; i < len(sshHosts); i++ { if sshHosts[i].Cmds != "" { sshHosts[i].CmdList = g.SplitString(sshHosts[i].Cmds) @@ -124,7 +131,7 @@ func main() { startTime := time.Now() log.Println("Multissh start") limitFunc := func(chLimit chan bool, ch chan g.SSHResult, host g.SSHHost) { - funcs.Dossh(host.Username, host.Password, host.Host, host.Key, host.CmdList, host.Port, *timeLimit, cipherList, host.LinuxMode, ch) + funcs.Dossh(host.Username, host.Password, host.Host, host.Key, host.CmdList, host.Port, *timeLimit, cipherList, keyExchangeList, host.LinuxMode, ch) <-chLimit } for i, host := range sshHosts { From 53850621a533bb9ce742ab819c1028c3a9ecfc13 Mon Sep 17 00:00:00 2001 From: Feng_Qi Date: Fri, 11 Feb 2022 15:50:19 +0800 Subject: [PATCH 13/15] README VERSION 0.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新 readme 和 example --- README.MD | 80 ++++++++++++++++++++++++++---------------------- ssh.json.example | 40 +++++++++++++----------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/README.MD b/README.MD index 0d96985..5cfb03f 100644 --- a/README.MD +++ b/README.MD @@ -29,40 +29,42 @@ https://github.com/shanghai-edu/multissh/releases/ # ./multissh -h Usage of ./multissh: -c string - cfg File Path + cfg File Path -ciphers string - ciphers + ciphers -cmdfile string - cmdfile path + cmdfile path -cmds string - cmds + cmds -f string - write file locate + write file locate -hostfile string - hostfile path + hostfile path -hosts string - host address list + host address list -ipfile string - ipfile path + ipfile path -ips string - ip address list - -j print output in json format + ip address list + -j print output in json format -k string - ssh private key - -l In linux mode,multi command combine with && ,such as date&&cd /opt&&ls + ssh private key + -keyexchanges string + keyexchanges + -l In linux mode,multi command combine with && ,such as date&&cd /opt&&ls -n int - max execute number (default 20) + max execute number (default 20) -outTxt - write result into txt + write result into txt -p string - password + password -port int - ssh port (default 22) + ssh port (default 22) -t int - max timeout (default 30) + max timeout (default 30) -u string - username - -v show version + username + -v show version ``` **cmdfile 示例** ``` @@ -82,24 +84,28 @@ show clock **ssh.json 示例** ``` { - "SshHosts": [ - { - "Host": "192.168.31.51", - "Port": 22, - "Username": "admin", - "Password": "admin", - "cmds":"show clock;show clock" - }, - { - "Host": "192.168.80.131", - "Port": 22, - "Username": "root", - "Password": "", - "key": "./server.key", - "linuxMode": true, - "CmdFile": "cmd2.txt.example" - } - ] + "SshHosts": [{ + "Host": "192.168.31.51", + "Port": 22, + "Username": "admin", + "Password": "admin", + "cmds": "show clock;show clock" + }, + { + "Host": "192.168.80.131", + "Port": 22, + "Username": "root", + "Password": "", + "key": "./server.key", + "linuxMode": true, + "CmdFile": "cmd2.txt.example" + } + ], + "Global": { + "Ciphers": "aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc", + "KeyExchanges": "diffie-hellman-group1-sha1,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1" + } + } ``` diff --git a/ssh.json.example b/ssh.json.example index bede1a5..01fa5ee 100644 --- a/ssh.json.example +++ b/ssh.json.example @@ -1,20 +1,24 @@ { - "SshHosts": [ - { - "Host": "192.168.31.51", - "Port": 22, - "Username": "admin", - "Password": "admin", - "cmds":"show clock;show clock" - }, - { - "Host": "192.168.80.131", - "Port": 22, - "Username": "root", - "Password": "", - "key": "./server.key", - "linuxMode": true, - "CmdFile": "cmd2.txt.example" - } - ] + "SshHosts": [{ + "Host": "192.168.31.51", + "Port": 22, + "Username": "admin", + "Password": "admin", + "cmds": "show clock;show clock" + }, + { + "Host": "192.168.80.131", + "Port": 22, + "Username": "root", + "Password": "", + "key": "./server.key", + "linuxMode": true, + "CmdFile": "cmd2.txt.example" + } + ], + "Global": { + "Ciphers": "aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc", + "KeyExchanges": "diffie-hellman-group1-sha1,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1" + } + } \ No newline at end of file From fa8ecd9a332e0bd4a2248c4f64f856f7e4c4cce3 Mon Sep 17 00:00:00 2001 From: TieWay59 Date: Sat, 11 Mar 2023 20:37:40 +0800 Subject: [PATCH 14/15] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90=20`connect`=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E7=BC=BA=E5=A4=B1=E7=9A=84=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 采用 nil 作为 `keyExchangeList` 从而提供默认的行为 --- funcs/ssh_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funcs/ssh_test.go b/funcs/ssh_test.go index 0ff1d7e..478d8b0 100644 --- a/funcs/ssh_test.go +++ b/funcs/ssh_test.go @@ -18,7 +18,7 @@ const ( func Test_SSH(t *testing.T) { var cipherList []string - session, err := connect(username, password, ip, key, port, cipherList) + session, err := connect(username, password, ip, key, port, cipherList, nil) if err != nil { t.Error(err) return From cfc416503f562b4e1a38f15cf961f80fe5c5cd50 Mon Sep 17 00:00:00 2001 From: TieWay59 Date: Sat, 11 Mar 2023 20:43:51 +0800 Subject: [PATCH 15/15] =?UTF-8?q?doc:=20=E5=A2=9E=E5=8A=A0=20`Test=5FSSH`?= =?UTF-8?q?=20=E5=87=BD=E6=95=B0=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- funcs/ssh_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/funcs/ssh_test.go b/funcs/ssh_test.go index 478d8b0..5dc124a 100644 --- a/funcs/ssh_test.go +++ b/funcs/ssh_test.go @@ -16,6 +16,10 @@ const ( key = "../server.key" ) +// Tests the SSH functionality of the package. +// +// It requires manual input of the local SSH private key path into the key +// variable, and the remote address into the ip variable. func Test_SSH(t *testing.T) { var cipherList []string session, err := connect(username, password, ip, key, port, cipherList, nil)