Compare commits

..

17 commits

Author SHA1 Message Date
Feng_Qi
3f32c3a325
Merge pull request #21 from TieWay59/fix-test-ssh
Fix:  not enough arguments in call to connect
2023-03-11 22:37:27 +08:00
TieWay59
cfc416503f doc: 增加 Test_SSH 函数描述 2023-03-11 20:43:51 +08:00
TieWay59
fa8ecd9a33 fix: 补齐 connect 函数缺失的参数数量
采用 nil 作为 `keyExchangeList` 从而提供默认的行为
2023-03-11 20:37:40 +08:00
Feng_Qi
53850621a5 README VERSION 0.4.0
更新 readme 和 example
2022-02-11 15:50:19 +08:00
Feng_Qi
770df7122e VERSION 0.4.0
支持在 sshHost 中配置 ciphers 和 keyExchanges 参数,以更好的适配不同的服务器环境
2022-02-11 15:26:40 +08:00
Feng_Qi
f369e796bf VERSION 0.3.0 2021-10-09 00:03:45 +08:00
Feng_Qi
8a6a859950
Merge pull request #12 from Dravening/master
fix:Adapt to some certain types of devices(switch).
2020-09-30 14:27:52 +08:00
Dravening
c7a73670a6
fix:Adapt to some certain types of devices(switch). 2020-08-11 16:29:23 +08:00
Feng_Qi
e704825aa9 0.2.3 2018-07-03 13:55:49 +08:00
Feng_Qi
de1b1f2b59 readme 2018-04-09 10:08:13 +08:00
Feng_Qi
82bd4c6577 version 0.2.2
add filelocate
2018-04-09 10:06:45 +08:00
Feng_Qi
360caeb82e README 2018-01-20 11:02:05 +08:00
Feng_Qi
378c12a285 0.2.1-version 2018-01-20 10:53:42 +08:00
Feng_Qi
87e207932f version 0.2 2018-01-19 17:54:08 +08:00
Feng_Qi
c1c790e653 0.1.2 readme 2018-01-05 13:35:27 +08:00
Feng_Qi
3b1a46e4e0 0.1.2
auto add exit  cmd
2018-01-05 13:28:08 +08:00
Feng_Qi
cddaeaaf8d 0.1.1 readme 2018-01-04 15:06:32 +08:00
17 changed files with 746 additions and 341 deletions

4
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.txt
*.json
*.exe
*.exe
*.key
*.zip

View file

@ -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.

329
README.MD
View file

@ -2,6 +2,15 @@
一个简单的并行 SSH 工具,可以批量的对主机通过 SSH 执行命令组合。
支持:
- 并发执行
- 单次执行多条命令
- ip 地址段自动匹配主机192.168.0.1-192.168.0.100
- ssh 用户名/密码认证
- ssh key 认证
- json 格式输出
- 输出到文本,文件名为 host.txt
#### 编译
```
go get ./...
@ -13,150 +22,296 @@ go build
提供 win64 和 linux64 两个平台的可执行文件
https://github.com/shanghai-edu/multissh/releases/tag/0.1
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
-f string
write file locate
-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
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
numLimit (default 20) //最大并发访问量 默认为20
-v show version
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 示例**
```
show clock
exit
```
**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 示例**
```
{
"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"
}
]
"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"
}
}
```
## 用法
#### cmd string & host string
```
./multissh -cmd "show clock;exit" -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
<sw-h3c>show clock
14:01:31 CN Wed 01/17/2018
Time Zone : CN add 08:00:00
<sw-h3c>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
<sw-h3c>show clock
14:01:31 CN Wed 01/17/2018
Time Zone : CN add 08:00:00
<sw-h3c>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. *
******************************************************************************
<sw-h3c>show clock
14:25:29 CN Wed 01/17/2018
Time Zone : CN add 08:00:00
<sw-h3c>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
<sw-h3c>show clock
14:29:41 CN Wed 01/17/2018
Time Zone : CN add 08:00:00
<WenKe-5F-Stack-2>show clock
14:29:41 CN Wed 01/17/2018
Time Zone : CN add 08:00:00
<WenKe-5F-Stack-2>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

View file

@ -1,2 +1 @@
show clock
exit
show clock

View file

@ -1,4 +1,3 @@
date
sleep 3
date
exit
cd /opt
sleep 2
ls

83
funcs/ssh_test.go Normal file
View file

@ -0,0 +1,83 @@
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
}
*/

201
funcs/sshconnect.go Normal file
View file

@ -0,0 +1,201 @@
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
}

View file

@ -1,4 +1,4 @@
package main
package g
import (
"bufio"
@ -12,6 +12,47 @@ 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 {
@ -40,20 +81,22 @@ 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
json.Unmarshal(b, &m)
result = m.SshHosts
err = json.Unmarshal(b, &result)
if err != nil {
log.Println("read file ", filePath, err)
return result, err
}
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, locate string) error {
outputFile, outputError := os.OpenFile(locate+sshResult.Host+".txt", os.O_WRONLY|os.O_CREATE, 0666)
if outputError != nil {
return outputError
}
@ -62,13 +105,27 @@ 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
}
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

13
g/const.go Normal file
View file

@ -0,0 +1,13 @@
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"
)

7
go.mod Normal file
View file

@ -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

10
go.sum Normal file
View file

@ -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=

View file

@ -1,2 +1,2 @@
192.168.15.101
192.168.15.110
192.168.31.21
192.168.15.102

View file

@ -1 +1,2 @@
192.168.15.101-192.168.15.110
192.168.15.101-192.168.15.110
192.168.31.21-192.168.31.22

157
main.go
View file

@ -1,155 +1,176 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
// "github.com/bitly/go-simplejson"
)
const (
VERSION = "0.1.1"
"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")
ips := flag.String("ips", "", "ip address list")
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")
keyExchanges := flag.String("keyexchanges", "", "keyexchanges")
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")
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")
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 []string
var hostList []string
var cmdList, hostList, cipherList, keyExchangeList []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.GetIpListFromFile(*ipFile)
if err != nil {
log.Println("load hostlist error: ", err)
log.Println("load iplist error: ", err)
return
}
}
if *hostFile != "" {
hostList, err = Getfile(*hostFile)
hostList, err = g.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 = 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 *keyExchanges != "" {
keyExchangeList = g.SplitString(*keyExchanges)
}
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 {
sshHostConfig, err := g.GetJsonFile(*cfgFile)
if err != nil {
log.Println("load jsonFile error: ", err)
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++ {
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, keyExchangeList, 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, *fileLocate)
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)
}
}

View file

@ -1,18 +1,24 @@
{
"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"
}
]
"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"
}
}

View file

@ -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
}

View file

@ -1,96 +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()
// 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
}