diff --git a/.gitignore b/.gitignore index e722b28..2cc6534 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ *.txt *.json -*.exe -*.key -*.zip \ No newline at end of file +*.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE index 535a350..e8e87ec 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 & ECNU] + Copyright [2017] [shanghai-edu] 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/README.MD b/README.MD index 5cfb03f..9441194 100644 --- a/README.MD +++ b/README.MD @@ -2,15 +2,6 @@ 一个简单的并行 SSH 工具,可以批量的对主机通过 SSH 执行命令组合。 -支持: -- 并发执行 -- 单次执行多条命令 -- ip 地址段自动匹配主机(192.168.0.1-192.168.0.100) -- ssh 用户名/密码认证 -- ssh key 认证 -- json 格式输出 -- 输出到文本,文件名为 host.txt - #### 编译 ``` go get ./... @@ -22,296 +13,150 @@ go build 提供 win64 和 linux64 两个平台的可执行文件 -https://github.com/shanghai-edu/multissh/releases/ +https://github.com/shanghai-edu/multissh/releases/tag/0.1 #### 命令体系 ``` -# ./multissh -h -Usage of ./multissh: - -c string - cfg File Path - -ciphers string - ciphers +./multissh -h + -cmd string + cmds // 需要执行的命令组合,多条命令以 ; 分割 -cmdfile string - cmdfile path - -cmds string - cmds - -f string - write file locate + cmdfile path //需要执行的命令组合文件,文件内命令按行分割 -hostfile string - hostfile path + hostfile path // 需要执行的主机列表文件,主机列表在文件内按行分割 -hosts string - host address list + host address list //需要执行的主机列表,多个主机以 ; 分割 -ipfile string - ipfile path - -ips string - ip address list - -j print output in json format - -k string - 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) - -outTxt - write result into txt + hostfile path //需要执行的主机(IP)列表文件,IP可以以地址段的方式逐行写在文本内 -p string - password + password // 主机的 SSH 密码 -port int - ssh port (default 22) - -t int - max timeout (default 30) + ssh port (default 22) //主机的 SSH 端口,默认 22 -u string - username - -v show version + 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 + -n int + numLimit (default 20) //最大并发访问量 默认为20 + -v show version + ``` **cmdfile 示例** ``` show clock +exit ``` **hostfile 示例** ``` -192.168.31.21 +192.168.15.101 192.168.15.102 ``` **ipfile 示例** ``` -192.168.15.101-192.168.15.103 -192.168.31.21-192.168.31.22 +192.168.15.101-192.168.15.102 ``` **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" - } - ], - "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" - } - + "SshHosts": [ + { + "Host": "192.168.15.101", + "Port": 22, + "Username": "admin", + "Password": "admin", + "CmdFile": "cmd1.txt.example" + }, + { + "Host": "192.168.83.40", + "Port": 22, + "Username": "root", + "Password": "root", + "CmdFile": "cmd2.txt.example" + } + ] } ``` ## 用法 #### cmd string & host string ``` -# ./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 ========= +./multissh -cmd "show clock;exit" -hosts "192.168.15.101;192.168.15.102" -u admin -p admin -****************************************************************************** -* 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 start +sw-1#show clock +05:26:40.649 UTC Tue Jun 6 2017 +sw-1#exit -show clock -14:01:31 CN Wed 01/17/2018 -Time Zone : CN add 08:00:00 -exit +192.168.15.101 ssh end -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 start +sw-2#show clock +05:24:38.708 UTC Tue Jun 6 2017 +sw-2#exit +192.168.15.102 ssh end ``` #### cmdfile & hostfile ``` -# ./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 ========= +./multissh -cmdfile cmd.txt -hostfile host.txt -u admin -p admin -****************************************************************************** -* 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 start +sw-1#show clock +05:29:43.269 UTC Tue Jun 6 2017 +sw-1#exit -show clock -14:01:31 CN Wed 01/17/2018 -Time Zone : CN add 08:00:00 -exit +192.168.15.101 ssh end -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 start +sw-2#show clock +05:27:41.332 UTC Tue Jun 6 2017 +sw-2#exit +192.168.15.102 ssh end ``` #### ipfile ``` -# ./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 ========= +./multissh -cmdfile cmd.txt -ipfile ip.txt -u admin -p admin -sw-cisco-1#show clock -06:17:49.422 UTC Wed Jan 17 2018 -sw-cisco-1#exit +192.168.15.101 ssh start +sw-1#show clock +05:29:43.269 UTC Tue Jun 6 2017 +sw-1#exit -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.101 ssh end -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 start +sw-2#show clock +05:27:41.332 UTC Tue Jun 6 2017 +sw-2#exit +192.168.15.102 ssh end ``` #### ssh.json ``` -./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 ========= +./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 -****************************************************************************** -* 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: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 +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 ``` +#### TODO +增加使用证书认证的支持 #### LICENSE Apache License 2.0 \ No newline at end of file diff --git a/g/cfg.go b/cfg.go similarity index 77% rename from g/cfg.go rename to cfg.go index cfe81be..7aa4d88 100644 --- a/g/cfg.go +++ b/cfg.go @@ -1,4 +1,4 @@ -package g +package main import ( "bufio" @@ -12,47 +12,6 @@ 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 - Global GlobalConfig -} - -type GlobalConfig struct { - Ciphers string - KeyExchanges string -} - -type SSHResult struct { - Host string - Success bool - Result string -} - -func SplitString(str string) (strList []string) { - if str == "" { - return - } - 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 { @@ -81,22 +40,20 @@ func Getfile(filePath string) ([]string, error) { } //gu -func GetJsonFile(filePath string) (HostJson, error) { - var result HostJson +func GetJsonFile(filePath string) ([]SSHHost, error) { + result := []SSHHost{} b, err := ioutil.ReadFile(filePath) if err != nil { log.Println("read file ", filePath, err) return result, err } - err = json.Unmarshal(b, &result) - if err != nil { - log.Println("read file ", filePath, err) - return result, err - } + var m HostJson + json.Unmarshal(b, &m) + result = m.SshHosts return result, nil } -func WriteIntoTxt(sshResult SSHResult, locate string) error { - outputFile, outputError := os.OpenFile(locate+sshResult.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666) +func WriteIntoTxt(sshHost SSHHost) error { + outputFile, outputError := os.OpenFile(sshHost.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666) if outputError != nil { return outputError } @@ -105,27 +62,13 @@ func WriteIntoTxt(sshResult SSHResult, locate string) error { outputWriter := bufio.NewWriter(outputFile) //var outputString string - outputString := sshResult.Result + outputString := sshHost.Result outputWriter.WriteString(outputString) outputWriter.Flush() return nil } -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) { +func GetIpList(filePath string) ([]string, error) { res, err := Getfile(filePath) if err != nil { return nil, nil diff --git a/cmd1.txt.example b/cmd1.txt.example index f58a975..f61b2a7 100644 --- a/cmd1.txt.example +++ b/cmd1.txt.example @@ -1 +1,2 @@ -show clock \ No newline at end of file +show clock +exit \ No newline at end of file diff --git a/cmd2.txt.example b/cmd2.txt.example index 71c5190..12a5f15 100644 --- a/cmd2.txt.example +++ b/cmd2.txt.example @@ -1,3 +1,4 @@ -cd /opt -sleep 2 -ls \ No newline at end of file +date +sleep 3 +date +exit \ No newline at end of file diff --git a/funcs/ssh_test.go b/funcs/ssh_test.go deleted file mode 100644 index 5dc124a..0000000 --- a/funcs/ssh_test.go +++ /dev/null @@ -1,83 +0,0 @@ -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" -) - -// 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) - 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 deleted file mode 100644 index fd8f359..0000000 --- a/funcs/sshconnect.go +++ /dev/null @@ -1,201 +0,0 @@ -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, keyExchangeList []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.Ciphers = []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"} - } else { - 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{ - 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, 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, keyExchangeList, chSSH) - } else { - go dossh_session(username, password, host, key, cmdlist, port, cipherList, keyExchangeList, 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, keyExchangeList []string, ch chan g.SSHResult) { - session, err := connect(username, password, host, key, port, cipherList, keyExchangeList) - 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, keyExchangeList []string, ch chan g.SSHResult) { - session, err := connect(username, password, host, key, port, cipherList, keyExchangeList) - 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/g/const.go b/g/const.go deleted file mode 100644 index 9e9db32..0000000 --- a/g/const.go +++ /dev/null @@ -1,13 +0,0 @@ -package g - -// changelog: -// 0.1 fisrt version -// 0.1.2 fix ssh error on h3c switch -// 0.2 -// 0.2.1 -// add write locate file -// json Unmarshal with error -// 0.2.3 -const ( - VERSION = "0.4.0" -) diff --git a/go.mod b/go.mod deleted file mode 100644 index 6dbc2b4..0000000 --- a/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 9bd8c9b..0000000 --- a/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -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= diff --git a/host.txt.example b/host.txt.example index 097f8b4..024af64 100644 --- a/host.txt.example +++ b/host.txt.example @@ -1,2 +1,2 @@ -192.168.31.21 -192.168.15.102 \ No newline at end of file +192.168.15.101 +192.168.15.110 \ No newline at end of file diff --git a/ip.txt.example b/ip.txt.example index 746458e..a99ff44 100644 --- a/ip.txt.example +++ b/ip.txt.example @@ -1,2 +1 @@ -192.168.15.101-192.168.15.110 -192.168.31.21-192.168.31.22 \ No newline at end of file +192.168.15.101-192.168.15.110 \ No newline at end of file diff --git a/main.go b/main.go index 7b7693f..ffdb520 100644 --- a/main.go +++ b/main.go @@ -1,176 +1,155 @@ package main import ( - "encoding/json" "flag" "fmt" "log" "os" - + "strconv" + "strings" "time" - - "github.com/shanghai-edu/multissh/funcs" - "github.com/shanghai-edu/multissh/g" + // "github.com/bitly/go-simplejson" ) +const ( + VERSION = "0.1.1" +) + +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") - ips := flag.String("ips", "", "ip address list") - cmds := flag.String("cmds", "", "cmds") + cmd := flag.String("cmd", "", "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") - keyExchanges := flag.String("keyexchanges", "", "keyexchanges") cmdFile := flag.String("cmdfile", "", "cmdfile path") hostFile := flag.String("hostfile", "", "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") + ipFile := flag.String("ipfile", "", "hostfile path") + //gu + jsonFile := flag.String("j", "", "Json File Path") 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") flag.Parse() - - var cmdList, hostList, cipherList, keyExchangeList []string + var cmdList []string + var hostList []string var err error - sshHosts := []g.SSHHost{} - var host_Struct g.SSHHost + sshHosts := []SSHHost{} + var host_Struct SSHHost + timeout := time.Duration(*timeLimit) * time.Second if *version { - fmt.Println(g.VERSION) + fmt.Println(VERSION) os.Exit(0) } if *ipFile != "" { - hostList, err = g.GetIpListFromFile(*ipFile) + hostList, err = GetIpList(*ipFile) if err != nil { - log.Println("load iplist error: ", err) + log.Println("load hostlist error: ", err) return } } if *hostFile != "" { - hostList, err = g.Getfile(*hostFile) + hostList, err = Getfile(*hostFile) if err != nil { log.Println("load hostfile error: ", err) return } } - if *ips != "" { - hostList, err = g.GetIpList(*ips) - if err != nil { - log.Println("load iplist error: ", err) - return - } - } - if *hosts != "" { - hostList = g.SplitString(*hosts) + hostList = strings.Split(*hosts, ";") } if *cmdFile != "" { - cmdList, err = g.Getfile(*cmdFile) + cmdList, err = Getfile(*cmdFile) if err != nil { log.Println("load cmdfile error: ", err) return } } - if *cmds != "" { - cmdList = g.SplitString(*cmds) - + if *cmd != "" { + cmdList = strings.Split(*cmd, ";") } - if *ciphers != "" { - cipherList = g.SplitString(*ciphers) - } - if *keyExchanges != "" { - keyExchangeList = g.SplitString(*keyExchanges) - } - if *cfgFile == "" { + if *jsonFile == "" { for _, host := range hostList { host_Struct.Host = host host_Struct.Username = *username host_Struct.Password = *password host_Struct.Port = *port - host_Struct.CmdList = cmdList - host_Struct.Key = *key - host_Struct.LinuxMode = *linuxMode + host_Struct.Cmd = cmdList sshHosts = append(sshHosts, host_Struct) } - } else { - sshHostConfig, err := g.GetJsonFile(*cfgFile) + } + //gu + if *jsonFile != "" { + sshHosts, err = GetJsonFile(*jsonFile) if err != nil { - log.Println("load cfgFile error: ", err) + log.Println("load jsonFile 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) - } else { - cmdList, err = g.Getfile(sshHosts[i].CmdFile) - if err != nil { - log.Println("load cmdFile error: ", err) - return - } - sshHosts[i].CmdList = cmdList + cmdList, err = Getfile(sshHosts[i].CmdFile) + if err != nil { + log.Println("load cmdFile error: ", err) + return } + sshHosts[i].Cmd = cmdList } } chLimit := make(chan bool, *numLimit) //控制并发访问量 - 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, keyExchangeList, host.LinuxMode, ch) + 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) <-chLimit } for i, host := range sshHosts { - chs[i] = make(chan g.SSHResult, 1) + chs[i] = make(chan string, 1) chLimit <- true go limitFunc(chLimit, chs[i], host) } - sshResults := []g.SSHResult{} - for _, ch := range chs { - res := <-ch - if res.Result != "" { - sshResults = append(sshResults, res) + 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.") } + + 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 _, sshResult := range sshResults { - err = g.WriteIntoTxt(sshResult, *fileLocate) + for i := 0; i < len(sshHosts); i++ { + err = WriteIntoTxt(sshHosts[i]) if err != nil { log.Println("write into txt error: ", err) return } } - return - } - if *jsonMode { - jsonResult, err := json.Marshal(sshResults) - if err != nil { - log.Println("json Marshal error: ", err) - } - fmt.Println(string(jsonResult)) - return - } - 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 01fa5ee..f18a812 100644 --- a/ssh.json.example +++ b/ssh.json.example @@ -1,24 +1,18 @@ { - "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" - } - + "SshHosts": [ + { + "Host": "192.168.15.101", + "Port": 22, + "Username": "admin", + "Password": "admin", + "CmdFile": "cmd1.txt.example" + }, + { + "Host": "192.168.83.40", + "Port": 22, + "Username": "root", + "Password": "root", + "CmdFile": "cmd2.txt.example" + } + ] } \ No newline at end of file diff --git a/ssh_test.go b/ssh_test.go new file mode 100644 index 0000000..93c615c --- /dev/null +++ b/ssh_test.go @@ -0,0 +1,53 @@ +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 new file mode 100644 index 0000000..8e0c4c9 --- /dev/null +++ b/sshconnect.go @@ -0,0 +1,96 @@ +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() + + // 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 + +}