├── .gitignore ├── LICENSE ├── README.md ├── README_CN.md ├── auth ├── auth.go ├── db │ └── query.go ├── driver │ └── driver.go └── web │ └── query.go ├── bin ├── 386 │ ├── linux │ │ ├── gossh │ │ └── passtool │ └── windows │ │ ├── gossh.exe │ │ └── passtool.exe ├── amd64 │ ├── linux │ │ ├── gossh │ │ └── passtool │ └── windows │ │ ├── gossh.exe │ │ └── passtool.exe └── arm64 │ └── linux │ ├── gossh │ └── passtool ├── build.sh ├── cmd ├── gossh │ └── gossh.go └── passtool │ └── passtool.go ├── config ├── parsefile.go └── parsefile_test.go ├── docs ├── README_en.md ├── example.md ├── faq.md ├── images │ ├── batchpass.png │ ├── gossh_function.png │ ├── gossh_qq.png │ ├── gossh_use.png │ └── singlepass.png ├── output_format.md ├── password.md ├── safe.md └── use_mysql_db.md ├── enc ├── aes.go ├── aes_test.go ├── key.go └── key_test.go ├── go.mod ├── go.sum ├── help └── help.go ├── logs └── wraplog.go ├── machine ├── server.go └── server_test.go ├── output ├── command.go └── json.go ├── run ├── run.go └── run_test.go ├── scp ├── README.md └── scp.go ├── sql └── db_init.sql └── tools ├── common.go ├── common_test.go └── hex └── hex.go /.gitignore: -------------------------------------------------------------------------------- 1 | #gitignore file# 2 | log/* 3 | ip.txt 4 | .gitignore 5 | gossh 6 | passtool 7 | 8 | #!bin/passtool 9 | 10 | #other rules 11 | #!bin/gossh 12 | #!bin/passtool 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gossh 2 | 3 | [中文](https://github.com/andesli/gossh/blob/master//README_CN.md) 4 | 5 | ## 1.What's gossh 6 | 7 | gossh is an extremely concise ssh tool which developed by go language. It has only a binary program without any dependencies and is really ready to use out of the box. 8 | gossh is used Used to manage of linux (like unix) machines: including remote execution of commands and push and pull files, and support stand-alone and batch modes. 9 | 10 | 11 | ## 2.What can gossh do 12 | 13 | Three core functions of gossh: 14 | 15 | 1. Connect to the remote host to execute commands. 16 | 2. Push local files or folders to remote hosts. 17 | 3. Pull files from the remote host to the local. 18 | 19 | ![功能](https://github.com/andesli/gossh/raw/master/docs/images/gossh_function.png) 20 | 21 | ## 3.gossh operating mode 22 | 23 | gossh supports stand-alone mode and batch parallel mode, that is, it can send commands to one machine at a time for execution, or batch commands to thousands of machines at a time. The batch parallel mode is also one of the biggest features of gossh, making full use of the advantages of the go language in concurrent execution. 24 | **Stand-alone mode**: 25 | The stand-alone mode supports the three functions mentioned above: remotely execute commands, push files or directories, and pull files. 26 | 27 | **Batch mode**: 28 | 29 | The ip file can be specified by the -i parameter, and the concurrency can be specified by -c parameter. The batch parallel mode also supports the three functions mentioned above: remotely execute commands, push files or directories, and pull files. 30 | 31 | ### Execution mode :concurrent and serial 32 | 33 | 1. The batch mode is controlled by -c by default. If -c is set to 1, the default is serial execution mode, and the value of -c is greater than 1 is parallel execution mode. 34 | 2. In parallel execution mode, a machine cannot be connected or execution fails and will not automatically exit. Serial mode is the same, but serial mode can make gossh exit immediately when an error occurs during execution through the -s parameter. 35 | 36 | The reason why the error exit is not provided in the parallel mode is that it is difficult to stop the execution of the entire task immediately under the parallel execution. The serial mode is easier to control. In daily use, you can use the serial mode verification function first, and then turn on the parallel mode to improve effectiveness. 37 | 38 | 39 | ## 4.Getting started 40 | 41 | ### 4.1Install 42 | 43 | **1.Building from source** 44 | 45 | ``` 46 | #To build gossh from the source code yourself you need to have a working Go environment with version 1.12 or greater installed. 47 | 48 | cd $GOPATH/src && git clone https://github.com/andesli/gossh.git 49 | cd gossh 50 | 51 | //build gossh 52 | go build ./cmd/gossh 53 | 54 | //build password encryption and decryption tool 55 | go build ./cmd/passtool 56 | 57 | 58 | //Compile the programs for windows and linux os under the amd64 386 architecture, which binarys is under the ./bin directory 59 | ./build.sh 60 | 61 | ``` 62 | 63 | **2.Pre-compiled binary** 64 | 65 | 66 | ``` 67 | bin 68 | |-- 386 69 | | |-- linux 70 | | | |-- gossh 71 | | | `-- passtool 72 | | `-- windows 73 | | |-- gossh.exe 74 | | `-- passtool.exe 75 | `-- amd64 76 | |-- linux 77 | | |-- gossh 78 | | `-- passtool 79 | `-- windows 80 | |-- gossh.exe 81 | `-- passtool.exe 82 | ``` 83 | 84 | [dowload](https://github.com/andesli/gossh/blob/master/bin) 85 | 86 | 87 | ### 4.2Usage 88 | 89 | - gossh 90 | 91 | ``` 92 | #gossh -h 93 | flag needs an argument: -h 94 | Usage of gossh: 95 | 96 | -t string 97 | running mode: cmd|push|pull (default "cmd") 98 | 99 | -h string 100 | ssh ip 101 | 102 | -P string 103 | ssh port (default "22") 104 | ssh端口 105 | 106 | -u string 107 | ssh user (default "root") 108 | ssh用户名 109 | 110 | -p string 111 | ssh password 112 | 密码 113 | 114 | 115 | -i string 116 | ip file when batch running mode (default "ip.txt") 117 | 118 | -c int 119 | the number of concurrency when b (default 30) 120 | 121 | -s if -s is setting, gossh will exit when error occurs 122 | 123 | -e password is Encrypted 124 | 125 | -key string 126 | aes key for password decrypt and encryption 127 | 128 | -f force to run even if it is not safe 129 | 130 | -s if -s is setting, gossh will exit when error occurs 131 | 132 | -l string 133 | log level (debug|info|warn|error (default "info") 134 | 135 | -logpath string 136 | logfile path (default "./log/") 137 | 138 | ``` 139 | - passtool tool 140 | 141 | ``` 142 | ./passtool -h 143 | Usage of ./passtool: 144 | -d Convert ciphertext to plaintext 145 | -e Convert plaintext to ciphertext 146 | -key string 147 | AES key 148 | ``` 149 | 150 | 151 | ### 4.3 Config file 152 | 153 | The -i parameter is used to specify the batch operation host ip file. Each line of the file has 4 fields ip|port|user|password, separated by |. The four fields are: machine IP, ssh port, ssh user name, ssh password. The ip field is required, and the other three fields are optional. The following configurations are all legal. 154 | 155 | ``` 156 | ip|port|user|password 157 | ip|port|user| 158 | ip|port|user 159 | ip|port| 160 | ip|port 161 | ip| 162 | ip 163 | ``` 164 | If no optional fields are provided, gossh obtains the command line parameters through the -u, -p, -P parameters by default. If no command line parameters are specified, the default values of the command line parameters are taken by default. The default value of the current parameters of gossh: 165 | 166 | ``` 167 | -u root 168 | -P 22 169 | -p default empty 170 | -t cmd 171 | 172 | ``` 173 | **Remark** 174 | 175 | - If the password field is empty, gossh will find the relevant process from the db plugin by default, refer to 5. 176 | - If the password field is encrypted, you need to specify the -e flag. -e is an overall switch: the passwords in the password file are either all encrypted or not. 177 | 178 | ### 4.4 Example 179 | 180 | [example](https://github.com/andesli/gossh/blob/master/docs/example.md)detail。 181 | 182 | ### 4.5 Log 183 | 184 | [logs](https://github.com/andesli/gossh/blob/master/docs/output_format.md)detail。 185 | 186 | 187 | ## 5.Password management 188 | 189 | [Password management](https://github.com/andesli/gossh/blob/master/docs/password.md)detail。 190 | 191 | ## 6.Security 192 | 193 | [Safety management](https://github.com/andesli/gossh/blob/master/docs/safe.md)detail 194 | 195 | 196 | ## 8.Scenes 197 | 198 | 1.The first initialization of a large-scale machine. 199 | 200 | The company came to hundreds of machines, only the ssh environment, except the initial user name and password, no other installation. At this time, use gossh to initialize the machine and establish a basic environment. (When gossh was originally written, it was to solve the environment initialization of Tencent pay DB thousands of machines). 201 | 202 | 2.Command-line batch remote management. 203 | 204 | Not every company is a BAT and has established an automated operation and maintenance management system. The operation and maintenance personnel of the vast majority of small and medium-sized enterprises manage machines remotely through scripts. They urgently need an ssh tool that can be used without any dependency. Gossh is prepared for this kind of people. Gossh does not require any configuration files, does not have any dependencies, and is really ready to use. 205 | 206 | 207 | ## 9. FAQ 208 | 209 | [FAQ](https://github.com/andesli/gossh/blob/master/docs/faq.md) 210 | 211 | Contact me for any questions 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # gossh 2 | 3 | ## 1.gossh是什么 4 | 5 | gossh是一个使用go语言开发的极度简洁的ssh工具,只有一个二进制程序,无任何依赖,真正开箱即用。用于远程管理linux(类unix)机器:包括远程执行命令和推拉文件,支持单机和批量模式。 6 | 7 | ## 2.gossh能干什么 8 | 9 | gossh提供3种核心功能: 10 | 11 | 1. 连接到远程主机执行命令。 12 | 2. 推送本地文件或者文件夹到远程主机。 13 | 3. 拉取远程主机的文件到本地。 14 | 15 | ![功能](https://github.com/andesli/gossh/raw/master/docs/images/gossh_function.png) 16 | 17 | ## 3.gossh运行模式 18 | 19 | gossh支持单机模式和批量并行模式,也就是可以一次向一台机器发送命令执行,也可以一次向成千上万台台机器批量发送命令。批量并行模式也是gossh最大的一个特点,充分利用go在并发执行方面的优势。 20 | 21 | 1. 单机模式。 22 | 单机模式支持上文说的三种功能:远程执行命令,推送文件或者目录,拉取文件。 23 | 24 | 2. 批量模式。 25 | 26 | 可以通过-i 参数指定ip文件,通过-c 指定并发度。 27 | 批量并行模式同样支持上文说的三种功能:远程执行命令,推送文件或者目录,拉取文件。 28 | 29 | ### 并行和串行执行 30 | 31 | 1. 批量模式默认通过-c控制并发度,如果-c 设置为1默认是串行执行模式, -c 的值大于1是并行执行模式。 32 | 2. 并行执行模式下某台机器连不上或者执行失败不会自动退出,串行模式也一样,但是串行模式通过-s 参数可以使gossh执行过程中出错立即退出。 33 | 34 | 并行模式下没有提供出错退出的原因是,并行执行下,很难立即停止整个任务的执行,串行模式比较容易控制,在日常使用中,可以先使用串行模式验证功能,然后开启并行模式提升效率。 35 | 36 | 37 | ## 4.gossh用法 38 | 39 | ### 4.1程序获取 40 | 41 | 1.源码编译。 42 | 43 | ``` 44 | #需要有go编译环境 45 | cd $GOPATH/src && git clone https://github.com/andesli/gossh.git 46 | cd gossh 47 | 48 | //gossh工具 49 | go build ./cmd/gossh 50 | 51 | //密码加解密工具 52 | go build ./cmd/passtool 53 | 54 | 55 | //编译脚本编译amd64 386体系结构下windows和linux版本,放到./bin目录下,如果有其他体系结构需要使用也可以修改脚本执行编译。 56 | ./build.sh 57 | 58 | ``` 59 | 60 | 2.如果不想从源码编译,编译好的二进制程序放bin/目录下。 61 | 62 | 得益于go语言优秀的跨平台特性,在./bin下已经为大家生成了amd64和386体系结构下windows和linux共计4个版本程序。 63 | 64 | ``` 65 | bin 66 | |-- 386 67 | | |-- linux 68 | | | |-- gossh 69 | | | `-- passtool 70 | | `-- windows 71 | | |-- gossh.exe 72 | | `-- passtool.exe 73 | `-- amd64 74 | |-- linux 75 | | |-- gossh 76 | | `-- passtool 77 | `-- windows 78 | |-- gossh.exe 79 | `-- passtool.exe 80 | ``` 81 | 82 | [点击立即下载](https://github.com/andesli/gossh/blob/master/bin) 83 | 84 | 85 | ### 4.2参数说明 86 | 87 | - gossh 88 | 89 | ``` 90 | #gossh -h 91 | flag needs an argument: -h 92 | Usage of gossh: 93 | 94 | -t string 95 | running mode: cmd|push|pull (default "cmd") 96 | 运行模式:cmd 远程执行命令,默认值;push 推送文件到远程; pull拉取远程文件到本地。 97 | 98 | -h string 99 | ssh ip 100 | 101 | -P string 102 | ssh port (default "22") 103 | ssh端口 104 | 105 | -u string 106 | ssh user (default "root") 107 | ssh用户名 108 | 109 | -p string 110 | ssh password 111 | 密码 112 | 113 | 114 | -i string 115 | ip file when batch running mode (default "ip.txt") 116 | 批量执行是指定ip文件,有关文件格式见下文。 117 | 118 | -c int 119 | the number of concurrency when b (default 30) 120 | 批量并发执行的并发度,默认值是30,如果指定为1,gossh是串行执行。 121 | 122 | -s if -s is setting, gossh will exit when error occurs 123 | -s是个bool型,只有到-c被指定为1时才有效,用来控制串行执行报错后是否立即退出。 124 | 125 | -e password is Encrypted 126 | 如果密码传递的是密文,使用-e标记。-e适用于通过-p传递的密码和-i 指定的文件中存放的密码字段。 127 | 128 | -key string 129 | aes key for password decrypt and encryption 130 | 密码加解密使用的key,gossh有一个默认的加密key,可以通过-key=xxx指定一个加解密的key. passtool密码加解密工具同样支持该-key选项. 131 | 132 | -f force to run even if it is not safe 133 | 如果遇到危险命令gossh默认是不执行,危险命令目前收录的有("rm", "mkfs", "mkfs.ext3", "make.ext2", "make.ext4", "make2fs", "shutdown", "reboot", "init", "dd"),可以通过-f强制执行,-f 是bool型参数,不指定默认为false。 134 | 135 | -s if -s is setting, gossh will exit when error occurs 136 | 如果-c=1,即并发度为1串行执行时,默认出错后会退出,使用-s标记不要退出,继续执行,在-c>1时,无论是否指定-s都不会出错退出。 137 | 138 | -l string 139 | log level (debug|info|warn|error (default "info") 140 | 日志级别 141 | 142 | -logpath string 143 | logfile path (default "./log/") 144 | 日志存放目录,默认是./log/ 145 | 146 | ``` 147 | - passtool密码工具 148 | 149 | ``` 150 | ./passtool -h 151 | Usage of ./passtool: 152 | -d 指定密码密文生成明文 153 | -e 指定密码明文生成密文 154 | -key string 155 | AES加密密钥 156 | ``` 157 | 158 | 159 | ### 4.3 批量运行时IP文件格式 160 | 161 | -i ipfile 指定批量操作的ip文件,ipfile文件每行有4个字段ip|port|user|password,字段之间使用|分隔,四个字段分别是:机器IP,ssh端口,ssh用户名,ssh密码。其中ip字段是必须的,其他三个字段是选填的。 162 | 下面的配置都是合法的。 163 | 164 | ``` 165 | ip|port|user|password 166 | ip|port|user| 167 | ip|port|user 168 | ip|port| 169 | ip|port 170 | ip| 171 | ip 172 | ``` 173 | 如果没有提供可选字段,gossh 默认通过-u -p -P参数从命令行参数获取,如果没有指定命令行参数,默认取命令行参数的默认值。 174 | gossh 当前参数的默认值: 175 | 176 | ``` 177 | -u 默认值是root 178 | -P 默认值是22 179 | -p 默认值是空 180 | -t 默认值是cmd 181 | 182 | ``` 183 | **说明** 184 | 185 | - 密码字段如果是空,gossh默认从db插件中查找相关流程参考第5章。 186 | - 如果密码字段加密了,需要指定-e标记。-e是个整体开关:密码文件中的密码要么全部加密,要么不加密。 187 | 188 | ### 4.4 详细示例 189 | 190 | 点击[示例](https://github.com/andesli/gossh/blob/master/docs/example.md)查看详情。 191 | 192 | ### 4.5 输出和日志 193 | 194 | 点击[输出和日志](https://github.com/andesli/gossh/blob/master/docs/output_format.md)查看详情。 195 | 196 | 197 | ## 5.密码管理 198 | 199 | 点击[密码管理](https://github.com/andesli/gossh/blob/master/docs/password.md)查看详情。 200 | 201 | ## 6.安全性 202 | 203 | gossh从多种角度保证执行安全,包括密码的加密存放、命令黑名单、以及文件传递过程中的检查、日志记录等,详情 204 | 点击[gossh安全管理](https://github.com/andesli/gossh/blob/master/docs/safe.md)查看详情。 205 | 206 | ## 7.不是重复造轮子 207 | 208 | gossh不是重复制造一个像ansible的轮子,gossh的核心目标是提供给运维人员一个极度简洁的ssh工具,方便运维人员远程批量并行的初始化和管理机器。 209 | 210 | 有很多同学说ansible已经够好的了,为什么还要搞gossh?这是一个误区,请问ansible怎么批量安装到机器上?python环境怎么批量安装?这里有一个“先有鸡还是先有蛋”的问题,gossh就是第一个会下蛋的鸡。gossh使用go语言开发,静态编译为二进制程序,只要你的机器有ssh环境,并且能密码可以登录,理论上都能使用gossh进行管理。 211 | 212 | gossh核心目标就是解决机器交付后“最初一公里-机器初始化的工作”。此时机器除了ssh,可能没有任何其他运行环境,此时通过gossh能够方便的快速的初始化机器。比如安装python,mysql等。 213 | 214 | 即便大公司在每个服务器上部署自研的agent,统一平台管理所有的服务器,但是也不能保证管理平台整个链路容灾高可用,gossh至少提供了一条备用的链路,能够在运维平台出问题的情况下,以闪电般的速度解决问题,从这个角度说,保留gossh这个最简单的通道,也算为运维人员提供一条消防通道。 215 | 216 | 当然gossh也提供了扩展,可以方便进行二次开发,将其改造为远程执行引擎,集成到公司的自动化系统中。 217 | 218 | ## 8.gossh适用场景。 219 | 220 | 1. 大规模机器的首次初始化。 221 | 公司来了几百台机器,只有ssh环境,除了初始用户名和密码,没有其他的安装。此时使用gossh对机器进行初始化,建立基本的环境。(gossh当初写的时候就是为了解决腾讯支付DB几千台机器的环境初始化)。 222 | 223 | 2. 命令行式批量远程管理。 224 | 不是每个公司都是BAT,建立起自动化的运维管理系统。占绝大多数的中小企业的运维人员是通过脚本在远程管理机器,他们迫切需要一个拿来就用,不需要任何依赖的ssh工具。gossh就是为这种人准备的,gossh不需要任何配置文件,没有任何依赖,真正做到拿来即用。 225 | 226 | 3. 将gossh二次开发,改造为一个远程执行引擎。目前gossh还没有实现,没有实现的原因有两点: 227 | 228 | - 这和最初将gossh设计为极简的ssh工具背道而驰。 229 | - 没有实际场景需求,大部分公司使用ansible或者salt stack, gossh无意重复造轮子。如果你的公司是一家使用go开发所有基础设施的云计算公司,可以在此基础上开发出一个远程执行引擎。 230 | 231 | **gossh目标** 232 | 233 | 1.第一阶段目标是提供一个极简、好用、无任何依赖、可并发执行的ssh命令行工具。 234 | 235 | 2.第二阶段目标是是实现一个高度可集成的远程执行引擎,对外提供API服务,完善点对点的文件传递功能,很大概率不会在现有的gossh程序上改造,避免gossh变的臃肿,而是提供一个gossh2或者gosshweb的工具,专门做这件事情,遵照go的哲学,少即是多,一次只做一件事情。 236 | 237 | 现阶段之关注第一阶段目标,第二阶段目标还在酝酿中。 238 | 239 | ## 9. FAQ 240 | 241 | [FAQ](https://github.com/andesli/gossh/blob/master/docs/faq.md) 242 | 243 | 任何问题可联系 , 感觉有用的话帮忙加个星。 244 | 245 | 为方便大家使用,提供了一个qq技术群:851647540, 手机qq可以直接扫描下方二维码。 246 | ![qq群](https://github.com/andesli/gossh/raw/master/docs/images/gossh_qq.png) 247 | 248 | 249 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package auth 18 | 19 | import ( 20 | "errors" 21 | "github.com/andesli/gossh/auth/driver" 22 | "sort" 23 | "sync" 24 | ) 25 | 26 | var ( 27 | driversMu sync.RWMutex 28 | drivers = make(map[string]driver.GetPassworder) 29 | ) 30 | 31 | //Register Password Source Driver 32 | func Register(name string, d driver.GetPassworder) { 33 | driversMu.Lock() 34 | defer driversMu.Unlock() 35 | 36 | if d == nil { 37 | panic("Register driver is nil") 38 | } 39 | if _, dup := drivers[name]; dup { 40 | panic("Register called twice for driver " + name) 41 | } 42 | drivers[name] = d 43 | } 44 | 45 | //List Password Source Drivers 46 | func Drivers() []string { 47 | driversMu.RLock() 48 | defer driversMu.RUnlock() 49 | var list []string 50 | for name := range drivers { 51 | list = append(list, name) 52 | } 53 | sort.Strings(list) 54 | return list 55 | } 56 | 57 | func unregisterAllDrivers() { 58 | driversMu.Lock() 59 | defer driversMu.Unlock() 60 | // For tests. 61 | drivers = make(map[string]driver.GetPassworder) 62 | } 63 | 64 | // get password 65 | func GetPassword(driverName, ip, user string) (string, error) { 66 | driversMu.Lock() 67 | defer driversMu.Unlock() 68 | d, ok := drivers[driverName] 69 | 70 | if !ok { 71 | return "", errors.New("unknown password driver: " + driverName) 72 | } 73 | return d.GetPassword(ip, user) 74 | } 75 | -------------------------------------------------------------------------------- /auth/db/query.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package db 18 | 19 | import ( 20 | "database/sql" 21 | _ "github.com/go-sql-driver/mysql" 22 | "github.com/andesli/gossh/auth" 23 | "github.com/andesli/gossh/enc" 24 | ) 25 | 26 | const ( 27 | dbtype = "mysql" 28 | ip = "localhost" 29 | port = "3306" 30 | user = "mysql_user" 31 | passwd = "mysql_pass" 32 | dbname = "cmdb" 33 | querysql = `select curPSW from t_password_info as A where A.hostName=? and A.userName= ? ` 34 | ) 35 | 36 | type DbDriver struct { 37 | dbtype string 38 | ip string 39 | port string 40 | user string 41 | password string 42 | dbname string 43 | sql string 44 | } 45 | 46 | func init() { 47 | db := &DbDriver{ 48 | dbtype: dbtype, 49 | ip: ip, 50 | port: port, 51 | user: user, 52 | password: passwd, 53 | dbname: dbname, 54 | sql: querysql, 55 | } 56 | auth.Register("db", db) 57 | } 58 | 59 | func (dv DbDriver) GetPassword(host, user string) (string, error) { 60 | 61 | dbhost := "tcp(" + dv.ip + ":" + dv.port + ")" 62 | conn := dv.user + ":" + dv.password + "@" + dbhost + "/" + dv.dbname + "?" + "" 63 | 64 | db, err := sql.Open(dv.dbtype, conn) 65 | if err != nil { 66 | return "", err 67 | } 68 | if err = db.Ping(); err != nil { 69 | return "", err 70 | } 71 | defer db.Close() 72 | 73 | stmt, err := db.Prepare(dv.sql) 74 | if err != nil { 75 | return "", err 76 | } 77 | defer stmt.Close() 78 | 79 | curPsw := "" 80 | err = stmt.QueryRow(host, user).Scan(&curPsw) 81 | 82 | if err != nil { 83 | return "", err 84 | } 85 | skey := enc.GetKey() 86 | 87 | psw, err := enc.AesDecEncode(curPsw, skey[:16]) 88 | 89 | return string(psw), err 90 | 91 | //maybe need decrypt the password 92 | //return Decrypt(curPsw,key) 93 | } 94 | -------------------------------------------------------------------------------- /auth/driver/driver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package driver 18 | 19 | type GetPassworder interface { 20 | GetPassword(ip, user string) (string, error) 21 | } 22 | -------------------------------------------------------------------------------- /auth/web/query.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "github.com/andesli/gossh/auth" 5 | ) 6 | 7 | type WebDriver struct { 8 | url string 9 | key string 10 | sql string 11 | } 12 | 13 | func init() { 14 | web := &WebDriver{} 15 | auth.Register("web", web) 16 | } 17 | 18 | func (web WebDriver) GetPassword(host, user string) (string, error) { 19 | //vist http api to get password 20 | // ..... 21 | return "password", nil 22 | } 23 | -------------------------------------------------------------------------------- /bin/386/linux/gossh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/386/linux/gossh -------------------------------------------------------------------------------- /bin/386/linux/passtool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/386/linux/passtool -------------------------------------------------------------------------------- /bin/386/windows/gossh.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/386/windows/gossh.exe -------------------------------------------------------------------------------- /bin/386/windows/passtool.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/386/windows/passtool.exe -------------------------------------------------------------------------------- /bin/amd64/linux/gossh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/amd64/linux/gossh -------------------------------------------------------------------------------- /bin/amd64/linux/passtool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/amd64/linux/passtool -------------------------------------------------------------------------------- /bin/amd64/windows/gossh.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/amd64/windows/gossh.exe -------------------------------------------------------------------------------- /bin/amd64/windows/passtool.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/amd64/windows/passtool.exe -------------------------------------------------------------------------------- /bin/arm64/linux/gossh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/arm64/linux/gossh -------------------------------------------------------------------------------- /bin/arm64/linux/passtool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/bin/arm64/linux/passtool -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | # build gossh and passtool 2 | # support architecture: amd64 386 3 | # suporrt os: linux and windows 4 | # authur: andes 5 | # email: email.tata@qq.com 6 | 7 | #!/bin/bash 8 | 9 | workhome=$(cd $(dirname $0) && pwd) 10 | binpath=${workhome}/bin 11 | 12 | oss=(linux windows) 13 | arches=(amd64 386 arm64) 14 | target=(gossh passtool) 15 | 16 | for arch in ${arches[@]};do 17 | for os in ${oss[@]};do 18 | for t in ${target[@]};do 19 | if [[ ${arch} == "arm64" && ${os} == "windows" ]];then 20 | continue 21 | fi 22 | cmd="CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build ${workhome}/cmd/${t}" 23 | echo "${cmd}" 24 | eval ${cmd} 25 | 26 | if [[ ! -d ${binpath}/${arch}/${os} ]];then 27 | mkdir -p ${binpath}/${arch}/${os} 28 | fi 29 | 30 | if [[ "${os}" == "windows" ]];then 31 | mv ${t}.exe ${binpath}/${arch}/${os} 32 | else 33 | mv ${t} ${binpath}/${arch}/${os} 34 | fi 35 | done 36 | done 37 | done 38 | 39 | -------------------------------------------------------------------------------- /cmd/gossh/gossh.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "github.com/andesli/gossh/enc" 23 | "github.com/andesli/gossh/help" 24 | "github.com/andesli/gossh/logs" 25 | "github.com/andesli/gossh/machine" 26 | "github.com/andesli/gossh/run" 27 | "github.com/andesli/gossh/tools" 28 | "path/filepath" 29 | "strings" 30 | "sync" 31 | ) 32 | 33 | //github.com/andesli/gossh version 34 | const ( 35 | AppVersion = "gossh 0.7" 36 | ) 37 | 38 | var ( 39 | 40 | //common options 41 | port = flag.String("P", "22", "ssh port") 42 | host = flag.String("h", "", "ssh ip") 43 | user = flag.String("u", "root", "ssh user") 44 | psw = flag.String("p", "", "ssh password") 45 | prunType = flag.String("t", "cmd", "running mode: cmd|push|pull") 46 | 47 | //batch running options 48 | ipFile = flag.String("i", "ip.txt", "ip file when batch running mode") 49 | cons = flag.Int("c", 30, "the number of concurrency when b") 50 | 51 | //safe options 52 | encFlag = flag.Bool("e", false, "password is Encrypted") 53 | force = flag.Bool("f", false, "force to run even if it is not safe") 54 | psafe = flag.Bool("s", false, "if -s is setting, gossh will exit when error occurs") 55 | pkey = flag.String("key", "", "aes key for password decrypt and encryption") 56 | blackList = []string{"rm", "mkfs", "mkfs.ext3", "make.ext2", "make.ext4", "make2fs", "shutdown", "reboot", "init", "dd"} 57 | 58 | //log options 59 | plogLevel = flag.String("l", "info", "log level (debug|info|warn|error") 60 | plogPath = flag.String("logpath", "./log/", "logfile path") 61 | log = logs.NewLogger() 62 | logFile = "gossh.log" 63 | 64 | pversion = flag.Bool("version", false, "gossh version") 65 | 66 | //Timeout 67 | ptimeout = flag.Int("timeout", 10, "ssh timeout setting") 68 | ) 69 | 70 | //main 71 | func main() { 72 | 73 | usage := func() { 74 | fmt.Println(help.Help) 75 | } 76 | 77 | flag.Parse() 78 | 79 | //version 80 | if *pversion { 81 | fmt.Println(AppVersion) 82 | return 83 | } 84 | 85 | if *pkey != "" { 86 | enc.SetKey([]byte(*pkey)) 87 | } 88 | 89 | if flag.NArg() < 1 { 90 | usage() 91 | return 92 | } 93 | 94 | if *prunType == "" || flag.Arg(0) == "" { 95 | usage() 96 | return 97 | } 98 | 99 | if err := initLog(); err != nil { 100 | fmt.Printf("init log error:%s\n", err) 101 | return 102 | } 103 | 104 | //异步日志,需要最后刷新和关闭 105 | defer func() { 106 | log.Flush() 107 | log.Close() 108 | }() 109 | 110 | log.Debug("parse flag ok , init log setting ok.") 111 | 112 | switch *prunType { 113 | //run command on remote server 114 | case "cmd": 115 | if flag.NArg() != 1 { 116 | usage() 117 | return 118 | } 119 | 120 | cmd := flag.Arg(0) 121 | 122 | if flag := tools.CheckSafe(cmd, blackList); !flag && *force == false { 123 | fmt.Printf("Dangerous command in %s", cmd) 124 | fmt.Printf("You can use the `-f` option to force to excute") 125 | log.Error("Dangerous command in %s", cmd) 126 | return 127 | } 128 | 129 | puser := run.NewUser(*user, *port, *psw, *force, *encFlag) 130 | log.Info("gossh -t=cmd cmd=[%s]", cmd) 131 | 132 | if *host != "" { 133 | log.Info("[servers]=%s", *host) 134 | run.SingleRun(*host, cmd, puser, *force, *ptimeout) 135 | 136 | } else { 137 | cr := make(chan machine.Result) 138 | ccons := make(chan struct{}, *cons) 139 | wg := &sync.WaitGroup{} 140 | run.ServersRun(cmd, puser, wg, cr, *ipFile, ccons, *psafe, *ptimeout) 141 | wg.Wait() 142 | } 143 | 144 | //push file or dir to remote server 145 | case "scp", "push": 146 | 147 | if flag.NArg() != 2 { 148 | usage() 149 | return 150 | } 151 | 152 | src := flag.Arg(0) 153 | dst := flag.Arg(1) 154 | log.Info("gossh -t=push local-file=%s, remote-path=%s", src, dst) 155 | 156 | puser := run.NewUser(*user, *port, *psw, *force, *encFlag) 157 | if *host != "" { 158 | log.Info("[servers]=%s", *host) 159 | run.SinglePush(*host, src, dst, puser, *force, *ptimeout) 160 | } else { 161 | cr := make(chan machine.Result, 20) 162 | ccons := make(chan struct{}, *cons) 163 | wg := &sync.WaitGroup{} 164 | run.ServersPush(src, dst, puser, *ipFile, wg, ccons, cr, *ptimeout) 165 | wg.Wait() 166 | } 167 | 168 | //pull file from remote server 169 | case "pull": 170 | if flag.NArg() != 2 { 171 | usage() 172 | return 173 | } 174 | 175 | //本地目录 176 | src := flag.Arg(1) 177 | //远程文件 178 | dst := flag.Arg(0) 179 | log.Info("gossh -t=pull remote-file=%s local-path=%s", dst, src) 180 | 181 | puser := run.NewUser(*user, *port, *psw, *force, *encFlag) 182 | if *host != "" { 183 | log.Info("[servers]=%s", *host) 184 | run.SinglePull(*host, puser, src, dst, *force) 185 | } else { 186 | run.ServersPull(src, dst, puser, *ipFile, *force) 187 | } 188 | 189 | default: 190 | usage() 191 | } 192 | } 193 | 194 | //setting log 195 | func initLog() error { 196 | switch *plogLevel { 197 | case "debug": 198 | log.SetLevel(logs.LevelDebug) 199 | case "error": 200 | log.SetLevel(logs.LevelError) 201 | case "info": 202 | log.SetLevel(logs.LevelInfo) 203 | case "warn": 204 | log.SetLevel(logs.LevelWarn) 205 | default: 206 | log.SetLevel(logs.LevelInfo) 207 | } 208 | 209 | logpath := *plogPath 210 | err := tools.MakePath(logpath) 211 | if err != nil { 212 | return err 213 | } 214 | 215 | logname := filepath.Join(logpath, logFile) 216 | logstring := `{"filename":"` + logname + `"}` 217 | 218 | //此处主要是处理windows下文件路径问题,不做转义,日志模块会报如下错误 219 | //logs.BeeLogger.SetLogger: invalid character 'g' in string escape code 220 | logstring = strings.Replace(logstring, `\`, `\\`, -1) 221 | 222 | err = log.SetLogger("file", logstring) 223 | if err != nil { 224 | return err 225 | } 226 | //开启日志异步提升性能 227 | log.Async() 228 | return nil 229 | } 230 | -------------------------------------------------------------------------------- /cmd/passtool/passtool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "github.com/andesli/gossh/enc" 23 | "os" 24 | ) 25 | 26 | var ( 27 | encry = flag.Bool("e", false, "encryption(加密)") 28 | decry = flag.Bool("d", false, "decrypt(解密)") 29 | pkey = flag.String("k", "", "aes加密密钥") 30 | ) 31 | 32 | func usage() { 33 | flag.PrintDefaults() 34 | os.Exit(1) 35 | } 36 | 37 | func main() { 38 | 39 | flag.Parse() 40 | 41 | if *pkey != "" { 42 | enc.SetKey([]byte(*pkey)) 43 | } 44 | 45 | if flag.NArg() < 1 { 46 | usage() 47 | } 48 | opstr := flag.Arg(0) 49 | 50 | rzkey := enc.GetKey() 51 | 52 | if *encry { 53 | text, err := fenc(opstr, rzkey) 54 | if err != nil { 55 | fmt.Printf("Error:%s\n", err) 56 | return 57 | } 58 | fmt.Println(text) 59 | } else if *decry { 60 | text, err := fdec(opstr, rzkey) 61 | if err != nil { 62 | fmt.Printf("Error:%s\n", err) 63 | return 64 | } 65 | fmt.Println(string(text)) 66 | 67 | } else { 68 | usage() 69 | } 70 | } 71 | 72 | func fdec(pass string, key []byte) ([]byte, error) { 73 | skey := key[:16] 74 | return enc.AesDecEncode(pass, skey) 75 | } 76 | func fenc(pass string, key []byte) (string, error) { 77 | skey := key[:16] 78 | return enc.AesEncEncode([]byte(pass), skey) 79 | } 80 | -------------------------------------------------------------------------------- /config/parsefile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package config 18 | 19 | import ( 20 | "bufio" 21 | "errors" 22 | "fmt" 23 | "github.com/andesli/gossh/enc" 24 | "io" 25 | "net" 26 | "os" 27 | "path/filepath" 28 | "strings" 29 | ) 30 | 31 | //Password Encrypted Key 32 | 33 | var ( 34 | rzkey = enc.GetKey() 35 | ) 36 | 37 | type Host struct { 38 | Ip string 39 | Port string 40 | User string 41 | Psw string 42 | } 43 | 44 | /* 45 | func main() { 46 | ipfile := os.Args[1] 47 | iplist, _ := ParseIps(ipfile, true) 48 | fmt.Printf("%v\n", iplist) 49 | } 50 | */ 51 | func GetIps(h []Host) []string { 52 | ips := make([]string, 0) 53 | for _, v := range h { 54 | ips = append(ips, v.Ip) 55 | } 56 | return ips 57 | } 58 | 59 | func PrintHosts(h []Host) { 60 | for _, v := range h { 61 | fmt.Printf("host=%s,port=%s,user=%s,password=%s\n", v.Ip, v.Port, v.User, v.Psw) 62 | } 63 | } 64 | 65 | func PaddingHosts(h []Host, port, user, psw string) []Host { 66 | 67 | hosts := make([]Host, 0) 68 | for _, v := range h { 69 | if v.Port == "" { 70 | v.Port = port 71 | } 72 | if v.User == "" { 73 | v.User = user 74 | } 75 | if v.Psw == "" { 76 | v.Psw = psw 77 | } 78 | 79 | hosts = append(hosts, v) 80 | } 81 | return hosts 82 | } 83 | 84 | /* 85 | ParseIps parse ip file to []Host 86 | 87 | ip file supported format: 88 | ip 89 | ip|port 90 | ip|port|username 91 | ip|port|username|password 92 | */ 93 | 94 | func ParseIps(ipfile string, eflag bool) ([]Host, error) { 95 | hosts := make([]Host, 0) 96 | 97 | AppPath, err := filepath.Abs(filepath.Dir(os.Args[0])) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | configfile := "" 103 | 104 | if filepath.IsAbs(ipfile) { 105 | configfile = ipfile 106 | } else { 107 | configfile = filepath.Join(AppPath, ipfile) 108 | } 109 | 110 | f, err := os.Open(configfile) 111 | 112 | if err != nil { 113 | return nil, err 114 | } 115 | defer f.Close() 116 | buf := bufio.NewReader(f) 117 | 118 | for { 119 | s, err := buf.ReadString('\n') 120 | if err != nil { 121 | //主要是兼容windows和linux文件格式 122 | if err == io.EOF && s != "" { 123 | goto Lable 124 | } else { 125 | return hosts, nil 126 | } 127 | return hosts, err 128 | } 129 | Lable: 130 | line := strings.TrimSpace(s) 131 | //if line[0] == '#' || line == "" { 132 | if line == "" || line[0] == '#' { 133 | continue 134 | } 135 | h, err := parseLine(line, eflag) 136 | if err != nil { 137 | continue 138 | // return hosts, err 139 | } 140 | hosts = append(hosts, h) 141 | } 142 | return hosts, err 143 | } 144 | 145 | func parseLine(s string, eflag bool) (Host, error) { 146 | host := Host{} 147 | line := strings.TrimSpace(s) 148 | 149 | if line[0] == '#' { 150 | return host, errors.New("comment line") 151 | } 152 | if line == "" { 153 | return host, errors.New("null line") 154 | } 155 | 156 | fields := strings.Split(line, "|") 157 | //ip := net.ParseIP(fields[0]) 158 | hname := strings.TrimSpace(fields[0]) 159 | _, err := net.LookupHost(hname) 160 | if err != nil { 161 | return host, errors.New("ill ip") 162 | } 163 | lens := len(fields) 164 | switch lens { 165 | case 1: 166 | host.Ip = hname 167 | case 2: 168 | host.Ip = hname 169 | host.Port = strings.TrimSpace(fields[1]) 170 | case 3: 171 | host.Ip = hname 172 | host.Port = strings.TrimSpace(fields[1]) 173 | host.User = strings.TrimSpace(fields[2]) 174 | case 4: 175 | host.Ip = hname 176 | host.Port = strings.TrimSpace(fields[1]) 177 | host.User = strings.TrimSpace(fields[2]) 178 | pass := strings.TrimSpace(fields[3]) 179 | if eflag && pass != "" { 180 | text, err := decrypt(pass, rzkey) 181 | if err != nil { 182 | return host, errors.New("decrypt the password error") 183 | } 184 | host.Psw = string(text) 185 | } else { 186 | host.Psw = pass 187 | } 188 | 189 | default: 190 | return host, errors.New("format err") 191 | } 192 | return host, nil 193 | } 194 | 195 | //decrypt password feild 196 | func decrypt(pass string, key []byte) ([]byte, error) { 197 | skey := key[:16] 198 | return enc.AesDecEncode(pass, skey) 199 | } 200 | -------------------------------------------------------------------------------- /config/parsefile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package config 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestGetIps(t *testing.T) { 24 | hosts := make([]Host, 0) 25 | host1 := Host{ 26 | Ip: "192.168.56.2", 27 | Port: "22", 28 | User: "root", 29 | Psw: "root", 30 | } 31 | host2 := Host{ 32 | Ip: "192.168.56.2", 33 | Port: "22", 34 | User: "root", 35 | Psw: "root", 36 | } 37 | hosts = append(hosts, host1, host2) 38 | 39 | h := GetIps(hosts) 40 | if len(h) != 2 { 41 | t.Fatal("TestGetIps Error") 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /docs/README_en.md: -------------------------------------------------------------------------------- 1 | # gossh 2 | 3 | ## What's gossh? 4 | 5 | gossh is a tool to excute command on a remote machine or push a file or dir to remote machine. It can run single or batch mode .It use ssh protocol. 6 | 7 | ## Usage: 8 | 9 | you'd better to add gossh to the system $PATH. 10 | ssh-comand:gossh -t cmd -h hostname -P port(default 22) -u username(default root) -p passswrod command 11 | ssh-scp: gossh -t scp -h hostname -P port(default 22) -u username(default root) -p passswrod localfile remotepath 12 | 13 | ## Note: 14 | 15 | 1.if -h hostname is not given ,the gossh read ip.txt file from current directory to get ip ,then run the command or transe files. The contents of ip.txt is the ip list separated by newline . 16 | 17 | 2.if -h hostname is given, only excute command on the -h hostname. 18 | 19 | 3.The host's password is get from password db automaticly,so register the password to the db before run command. 20 | You can also use the '-p password' to given the password if you havn't registered the password to the db. 21 | 22 | 4.The default port is 22, you can change it by the '-P port' option. 23 | 24 | 5.The default user is root , you can change it by the '-u user' option. 25 | 26 | ## Warning: 27 | 1.You must make sure your command is safe ,because the comand is default running in batch mode. 28 | 29 | 2.Before you use 'gossh -t scp' to transfer files,make sure the remotepath is exist.It rewrite the exist file in the remote server default, make backup before run the command . 30 | 31 | 3.When you use 'gossh -t scp ' and not given the fullpath of localfile, it find the localfile an current path default. 32 | 33 | 3.You also can transfer the whole directory to use 'gossh -t scp '. 34 | 35 | ## Example: 36 | 1.show ip list of the server 10.238.48.101 37 | gossh -t cmd -h 10.238.48.101 "ip addr list" 38 | 39 | 2.show ip list of all server in the current path ip.txt file. 40 | gossh -t cmd "ip addr list" 41 | 42 | 3.Transfer the local file ip.txt of current path to the server 10.238.48.101:/data/tmp 43 | gossh -t scp -h 10.238.48.101 ip.txt /data/tmp 44 | 45 | 4.Transfer the local file ip.txt of current path to all servers's /data/tmp 46 | gossh -t scp ip.txt /data/tmp 47 | 48 | -------------------------------------------------------------------------------- /docs/example.md: -------------------------------------------------------------------------------- 1 | # gossh使用示例 2 | 3 | ## 1.单机模式 4 | 5 | 1.远程运行命令。 6 | 7 | - 命令原型 8 | 9 | ``` 10 | gossh [-t cmd] -h hostname -P port(default 22) -u username(default root) -p passswrod [-f] "command" 11 | 12 | ``` 13 | - -t 参数可以省略,gossh默认执行模式就是远程执行命令。 14 | - command远程执行命令如果有空格,建议使用双引号将括起来。 15 | - -P -u 都可以不指定,使用默认值。 16 | 17 | - 示例 18 | 19 | ``` 20 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 -t cmd -u root -p xxxx -P 22 "uname" 21 | ip=192.168.56.2 22 | command=uname 23 | return=0 24 | Linux 25 | 26 | ---------------------------------------------------------- 27 | 28 | ``` 29 | - 使用默认值 30 | 31 | 只指定了-h 主机,-t -u -P都使用默认值。 32 | 33 | ``` 34 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 "uname" 35 | ip= 192.168.56.2 36 | command= uname 37 | Linux 38 | ``` 39 | 40 | - 使用-f强制执行命令,一些危险命令拒绝执行,使用-f强制执行。 41 | 42 | ``` 43 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 "cd /tmp && rm ip.txt" 44 | Dangerous command in cd /tmp && rm ip.txt 45 | You can use the `-f` option to force to excute 46 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 -f "cd /tmp && rm ip.txt" 47 | ip=192.168.56.2 48 | command=cd /tmp && rm ip.txt 49 | return=0 50 | 51 | ---------------------------------------------------------- 52 | ``` 53 | 54 | 2.推送文件到远程主机。 55 | 56 | 支持推送文件或者文件夹到远程主机,如果远程主机已经存在文件,默认是拒绝执行,可以使用-f参数强制覆盖。 57 | 58 | - 命令原型 59 | 60 | ``` 61 | gossh -t push -h hostname -P port(default 22) -u username(default root) -p passswrod [-f] localfile/localpath remotepath 62 | 63 | ``` 64 | 65 | 如果远程文件已存在,可以使用-f参数强制覆盖。 66 | 67 | ``` 68 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 -t push ip.txt /tmp 69 | ip=192.168.56.2 70 | command=push ip.txt to 192.168.56.2:/tmp 71 | return=1 72 | 73 | Remote Server's /tmp has the same file ip.txt 74 | You can use `-f` option force to cover the remote file. 75 | 76 | 77 | ---------------------------------------------------------- 78 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 -t push -f ip.txt /tmp 79 | ip=192.168.56.2 80 | command=push ip.txt to 192.168.56.2:/tmp 81 | return=0 82 | push ip.txt to 192.168.56.2:/tmp ok 83 | 84 | ---------------------------------------------------------- 85 | ``` 86 | 87 | 3.从远程主机拉取文件。 88 | 89 | - 命令原型 90 | 91 | ``` 92 | gossh -t pull -h hostname -P port(default 22) -u username(default root) -p passswrod remote_file local_path 93 | 94 | ``` 95 | 96 | 注意:如果local_path不存在,会自动创建。 97 | 98 | - 完整示例 99 | 100 | ``` 101 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 -u root -p xxxxx -P 22 -t pull /tmp/ip.txt . 102 | ip= 192.168.56.2 103 | command= scp root@192.168.56.2:/tmp/ip.txt . 104 | Files is transferred successfully. 105 | 106 | ``` 107 | 108 | ## 2.批量模式 109 | 110 | 批量模式和单机模式的区别: 111 | 112 | 1. 使用-i参数代替-h指定批量运行的ip文件。 113 | 2. 可以是用-c参数指定并发度。 114 | 3. 如果密码加密了,需要带上-e参数告诉gossh密码已经加密。 115 | 4. 批量拉取文件到本地时,由于是文件都一样,文件会放到local_path/ip下,单机模式时直接放在local_path指定本地目录,但是批量模式,放到local_path下每个主机ip子目录下。 116 | 117 | ipfile文件内容(密码加密): 118 | 119 | ``` 120 | [root@andesli.com /project/go/src/gossh]#cat ip.txt 121 | 192.168.56.2|22|root|T9GrQBSkD6zkRZOEd+ggfg== 122 | 192.168.56.2|22|root| 123 | 192.168.56.2|22 124 | 192.168.56.2| 125 | 126 | ``` 127 | 128 | 129 | 1.批量远程运行命令。 130 | 131 | ``` 132 | [root@andesli.com /project/go/src/gossh]#gossh -t cmd -i ip.txt "uname" 133 | [servers]=[192.168.56.2 192.168.56.2] 134 | ip=192.168.56.2 135 | command=uname 136 | return=0 137 | Linux 138 | 139 | ---------------------------------------------------------- 140 | ip=192.168.56.2 141 | command=uname 142 | return=0 143 | Linux 144 | 145 | ---------------------------------------------------------- 146 | ``` 147 | 148 | 2.推送文件到批量远程主机。 149 | 150 | ``` 151 | [root@andesli.com /project/go/src/gossh]#gossh -t push -f -i ip.txt passtool /tmp 152 | [servers]=[192.168.56.2 192.168.56.2] 153 | ip=192.168.56.2 154 | command=push passtool to 192.168.56.2:/tmp 155 | return=0 156 | push passtool to 192.168.56.2:/tmp ok 157 | 158 | ---------------------------------------------------------- 159 | ip=192.168.56.2 160 | command=push passtool to 192.168.56.2:/tmp 161 | return=0 162 | push passtool to 192.168.56.2:/tmp ok 163 | 164 | ---------------------------------------------------------- 165 | ``` 166 | 167 | 3.批量从远程主机拉取文件到本地。 168 | 169 | ``` 170 | [root@andesli.com /project/go/src/gossh]#gossh -t pull -f -i ip.txt /project/go/src/gossh/passtool /tmp 171 | [servers]=[192.168.56.2 192.168.56.2] 172 | ip= 192.168.56.2 173 | command= scp root@192.168.56.2:/project/go/src/gossh/passtool /tmp/192.168.56.2 174 | return=0 175 | Pull from /project/go/src/gossh/passtool to /tmp/192.168.56.2 ok. 176 | ---------------------------------------------------------- 177 | ip= 192.168.56.2 178 | command= scp root@192.168.56.2:/project/go/src/gossh/passtool /tmp/192.168.56.2 179 | return=0 180 | Pull from /project/go/src/gossh/passtool to /tmp/192.168.56.2 ok. 181 | ---------------------------------------------------------- 182 | ``` 183 | 184 | 注意: 185 | 186 | -h hostname 指定主机,用于单机模式。 187 | -i ipfile 指定ip文件,用于批量模式。 188 | 189 | 如果既不指定-h 也不指定-i参数,gossh默认会从当前目录中寻找ip.txt文件作为ip文件进行批量模式执行。 190 | 191 | 文件推送或者拉取过程中,设计到文件是否存在的判断,以及是否覆盖的判断,详细规则见[gossh安全管理](https://github.com/andesli/gossh/blob/master/docs/safe.md)第3节文件传递安全性。 192 | 193 | ## 3 使用总结 194 | 195 | gossh可以完成简单的命令执行,文件传递等工作,也可以完成复查的工作。完成复杂工作需要在本地编写脚本,推送脚本文件,然后远程执行脚本,再将脚本执行结果文件拉取到本地分析处理。 196 | 197 | ![用法](https://github.com/andesli/gossh/raw/master/docs/images/gossh_use.png) 198 | 199 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | 1.gossh是否支持拉取远程目录? 4 | 目前gossh还不支持拉取远程机器的目录到本地,建议先将远程机器目录压缩成文件,然后在拉取。 5 | 6 | 2.gossh远程命令中的引号有什么好的建议? 7 | 执行命令comand如果较复杂,中间有空格,或者引号,使用原则是: 8 | 9 | - 使用双引号将command整个引用起来 "command" ,此时command中如包含双引号需要使用\进行转义(\"),含有的单引号不需要任何处理,直接使用; 10 | 11 | - 不建议使用单引号将 command扩其来 ,原因是单引号中不能再引用单引号(引号就近匹配原则,且其内部引用的东西不做任何转义,导致command书写的灵活性降低大大降低,特别是在脚本中。) 12 | 13 | 示例: 14 | 15 | ``` 16 | gossh -t cmd "ps -ef |egrep \"(mysql|master|slave|time|keep)\"" 17 | gossh -t cmd "ps -ef |egrep '(mysql|master|slave|time|keep)'" 18 | 19 | ``` 20 | 3.执行报"GET PASSWORD ERROR"原因。 21 | 22 | gossh优先从ip文件中获取密码,然后从命令行获取密码,最后试图从db获取密码。如果你没有配置DB相关环境,也没有在命令行和IP文件中指定密码,gossh获取密码失败会报该错误。 23 | 24 | 4.gossh运行平台。 25 | 26 | gossh使用go语言编写,理论上只要go语言支持的平台都能使用gossh,但是由于gossh使用的是ssh2协议,被gossh管理的机器必须支持ssh2协议,gossh最适合管理linux系统,windows系统没有经过测试。 27 | 28 | 29 | 5.任何问题请联系 email.tata@qq.com 30 | 31 | 为方便大家使用,提供了一个qq技术群:851647540, 可直接扫描下方二维码。 32 | ![qq群](https://github.com/andesli/gossh/raw/master/docs/images/gossh_qq.png) 33 | -------------------------------------------------------------------------------- /docs/images/batchpass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/docs/images/batchpass.png -------------------------------------------------------------------------------- /docs/images/gossh_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/docs/images/gossh_function.png -------------------------------------------------------------------------------- /docs/images/gossh_qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/docs/images/gossh_qq.png -------------------------------------------------------------------------------- /docs/images/gossh_use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/docs/images/gossh_use.png -------------------------------------------------------------------------------- /docs/images/singlepass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andesli/gossh/10b7c71501dd3cd622980f7eba2c17a37068ecac/docs/images/singlepass.png -------------------------------------------------------------------------------- /docs/output_format.md: -------------------------------------------------------------------------------- 1 | # gossh输出打印格式 2 | 3 | ## 标准输出 4 | 5 | 1.gossh远程执行命令返回格式. 6 | 7 | ``` 8 | #批量模式首行首先打印所有的远程机器IP. 9 | [servers]=[192.168.56.2 192.168.56.2] 10 | #机器ip 11 | ip=xxx.xxx.56.2 12 | #远程执行命令 13 | command=uname 14 | #命令执行完后的退出值,就是$? 15 | return=0 16 | #远程执行命令输出到标准输出和错误输出的结果 17 | Linux 18 | 19 | ##换行和---分隔线 20 | 21 | ---------------------------------------------------------- 22 | ``` 23 | 24 | 下面是一个简单的示例: 25 | 26 | ``` 27 | [root@andesli.com /project/go/src/gossh]#gossh "uname" 28 | [servers]=[192.168.56.2 192.168.56.2] 29 | ip=192.168.56.2 30 | command=uname 31 | return=0 32 | Linux 33 | 34 | ---------------------------------------------------------- 35 | ip=192.168.56.2 36 | command=uname 37 | return=0 38 | Linux 39 | 40 | ---------------------------------------------------------- 41 | ``` 42 | 远程执行命令在实际使用中的两个范式: 43 | 44 | 1. 如果是临时性的任务,一般gossh结合grep能够很方便的判断批量执行的结果。 45 | 2. 如果是正式任务或者复杂任务,建议将逻辑封装到一个脚本文件中,push到远程主机,然后再执行。 46 | 47 | 2.gossh推送和拉取文件输出结果和远程执行命令格式类似。 48 | 49 | 通过return=0判断推送或者拉取文件成功。 50 | 51 | - push文件 52 | 53 | ``` 54 | [root@andesli.com /project/go/src/gossh]#gossh -t push passtool /tmp 55 | [servers]=[192.168.56.2 192.168.56.2] 56 | ip=192.168.56.2 57 | command=push passtool to 192.168.56.2:/tmp 58 | return=0 59 | push passtool to 192.168.56.2:/tmp ok 60 | 61 | ---------------------------------------------------------- 62 | ip=192.168.56.2 63 | command=push passtool to 192.168.56.2:/tmp 64 | return=0 65 | push passtool to 192.168.56.2:/tmp ok 66 | 67 | ---------------------------------------------------------- 68 | ``` 69 | - pull文件 70 | 71 | ``` 72 | [root@andesli.com /project/go/src/gossh]#gossh -t pull -f /project/go/src/gossh/passtool /tmp 73 | [servers]=[192.168.56.2 192.168.56.2] 74 | ip= 192.168.56.2 75 | command= scp root@192.168.56.2:/project/go/src/gossh/passtool /tmp/192.168.56.2 76 | return=0 77 | Pull from /project/go/src/gossh/passtool to /tmp/192.168.56.2 ok. 78 | ---------------------------------------------------------- 79 | ip= 192.168.56.2 80 | command= scp root@192.168.56.2:/project/go/src/gossh/passtool /tmp/192.168.56.2 81 | return=0 82 | Pull from /project/go/src/gossh/passtool to /tmp/192.168.56.2 ok. 83 | ---------------------------------------------------------- 84 | 85 | ``` 86 | 87 | 88 | ## 2.日志 89 | 90 | 1. gossh日志名默认是gossh.log ,该文件默认位于执行gossh当前目录的./log内(./log/gossh.log),可以通过-logpath path 选项指定日志文件位置,如果目录不存在,gossh自动创建该目录,暂时不支持修改日志文件名。 91 | 92 | 2. 支持如下日志级别debug|info|warn|error, 默认是info级别,可以通过-l 选项指定日志级别。 93 | 94 | 3. gossh日志不会打印到标准输出里面,仅仅作为审计使用。 95 | 96 | gossh日志模块使用的是beego的日志模块,采用异步记录模式,详见[https://github.com/astaxie/beego/tree/master/logs](https://github.com/astaxie/beego/tree/master/logs),beego日志模块具有良好的扩展性,稍许定制就可以方便的将日志输出到文件,邮件和数据库中。 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/password.md: -------------------------------------------------------------------------------- 1 | # 密码管理 2 | 3 | ## 1.密码安全 4 | 5 | gossh批量操作时,将密码明文存放到配置文件中有时不太妥。gossh提供加密存放方式。通过-e开关,默认是不加密存放,-e参数代表当前的IP配置文件是加密后存放的。 6 | 7 | 为此提供了一个专门的加解密工具passtool。 8 | 9 | ``` 10 | [root@andesli.com /project/go/src/gossh]#./passtool 11 | -d 指定密码密文生成明文 12 | -e 指定密码明文生成密文 13 | -key string 14 | 加密密钥 15 | ``` 16 | 如果是长期运行的脚本,建议将ip中的密码加密处理。 17 | 18 | ## 2.密码获取 19 | 20 | gossh对于密码的支持比较灵活,可以通过-p参数指定,批量模式下可以在ip文件中指定,ip文件中的的密码支持加密。如果上述都没有指定,gossh还默认的可以通过插件方式从db或者通过外部系统api获取密码。 21 | 22 | - 单机模式密码获取流程 23 | 24 | ![单机模式](https://github.com/andesli/gossh/raw/master/docs/images/singlepass.png) 25 | 26 | - 批量模式模式密码获取流程 27 | 28 | ![批量模式](https://github.com/andesli/gossh/raw/master/docs/images/batchpass.png) 29 | 30 | ## 3.密码扩展 31 | 32 | gossh支持密码插件的方式访问密码,通过定义一套标准的密码获取接口,外部插件只要实现该接口,就能注册进去。gossh内部默认实现了一个通过db获取密码的插件。 33 | 34 | 1. 通过访问db获取密码。 35 | gossh 提供了一个简单的默认实现,如果不指定操作的机器密码,gossh默认会访问:[gossh/auth/db/query.go](https://github.com/andesli/gossh/blob/master/auth/db/query.go#L10)中指定的db库表中查询。 36 | 37 | ``` 38 | 10 const ( 39 | 11 dbtype = "mysql" 40 | 13 ip = "localhost" 41 | 14 port = "3306" 42 | 15 user = "mysql_user" 43 | 16 passwd = "mysql_pass" 44 | 17 dbname = "cmdb" 45 | 46 | ``` 47 | db库表初始化sql参见 [gossh/sql/db_init.sql](https://github.com/andesli/gossh/blob/master/sql/db_init.sql) 48 | 49 | [gossh通过db获取密码环境搭建过程](https://github.com/andesli/gossh/blob/master/docs/use_mysql_db.md) 50 | 51 | 2. 通过web api方式。 52 | 该种方法只写了框架,需要的同学可以将其与自己的密码管理系统对接。 53 | 54 | ## 4.密码插件原理 55 | 56 | gossh这个密码获取实现参照go标准库database/sql的设计思想,主要代码在auth目录。任何实现了如下接口的密码获取组件都可以注册到drivers里面。 57 | 58 | - 密码接口,gossh/auth/driver/driver.go 59 | ``` 60 | type GetPassworder interface { 61 | GetPassword(ip, user string) (string, error) 62 | } 63 | ``` 64 | 65 | - 注册接口. 66 | ``` 67 | // gossh/auth/auth.go 68 | 69 | drivers = make(map[string]driver.GetPassworder) 70 | func Register(name string, d driver.GetPassworder) 71 | 72 | ``` 73 | - 密码存放到db的注册实现. 74 | 75 | ``` 76 | func init() { 77 | db := &DbDriver{ 78 | dbtype: dbtype, 79 | ipport: ipport, 80 | user: user, 81 | password: passwd, 82 | dbname: dbname, 83 | sql: querysql, 84 | } 85 | auth.Register("db", db) 86 | } 87 | ``` 88 | 89 | - 注册或者修改密码获取组件。 90 | ``` 91 | // gossh/machine/server.go 92 | 93 | //加载注册实现了GetPassworder密码访问组件 94 | _ "gossh/auth/db" 95 | //_ "gossh/auth/web" 96 | 97 | //指定从那个密码源读取密码 98 | PASSWORD_SOURCE = "db" 99 | //PASSWORD_SOURCE = "web" 100 | 101 | ``` 102 | ## 5. 加密key 103 | 104 | 加解密密码的默认key存放在 [key](https://github.com/andesli/gossh/blob/master/enc/key.go) ,gossh和passtool都支持通过-key选项指定加解密的key。 105 | 106 | 107 | 注意: 108 | 109 | 1. -key指定的key要求有16个字节,如果key不够16个字节程序自动会在后面填充"0",如果超过16个字节,程序自动会截取16字节长度。 110 | 2. 目前存放在db里面的机器密码也是使用该key加解密。 111 | 112 | ## 6.SSH2登录认证 113 | 114 | SSH2协议有三种登录方式,分别是: 115 | 116 | ``` 117 | Password 118 | Keyboard Interaction 119 | Public Key 120 | ``` 121 | gossh支持 Password 和Keyboard Interaction,理论上只要linux机器支持密码登录,都可以使用gossh进行管理,当然你也可以通过gossh为机器配置Public Key。 122 | 123 | -------------------------------------------------------------------------------- /docs/safe.md: -------------------------------------------------------------------------------- 1 | # gossh安全性 2 | 3 | ## 1.远程执行命令 4 | 5 | ### 1.1 危险命令检测. 6 | 7 | gossh将危险的命令放到黑名单中,一旦远程执行危险命令,会自动退出,通过指定-f参数强制执行。危险命令目前收录如下: 8 | 9 | ``` 10 | "mount", "umount", "rm", "mkfs", "mkfs.ext3", "make.ext2", "make.ext4", "make2fs", "shutdown", "reboot", "init", "dd" 11 | ``` 12 | 13 | ### 1.2 串行命令执行. 14 | 15 | gossh为了提升效率,采用了并发执行模式,通过-c参数控制并发度,-c的默认值是30,当然也支持串行执行,-c=1就是串行执行,串行执行是如果遇到失败,gossh会自动退出,不会再继续执行,这也是一种安全防护,可以使用-s参数强制遇到执行失败仍然继续执行。非串行模式,gossh遇到一个host执行错误,不会退出,这是因为gossh内部是并发执行的,有一个出错,其他的并发执行的任务很难停下来。在实际使用gossh开发的过程中,建议先使用gossh -c=1 进行串行的调试,带安全测试后,再改为并行执行模式。 16 | 17 | ## 2.密码安全 18 | 19 | gossh支持密码加密存放,如果指定了-e标记,就代表传递给gossh的密码密文。-e开关不但对单机执行有效,-i指定的ip文件里的存放的密码也被认为是加密存放的,密码的加解密可以使用passtool工具处理。 20 | 21 | - gossh使用-key指定加解密key。 22 | 23 | ``` 24 | [root@andesli.com /project/go/src/gossh]#gossh -h 192.168.56.2 -p="5f9lPu0eHz98lRsnpo+oHw==" -key="tata" -e "uname" 25 | ip=192.168.56.2 26 | command=uname 27 | return=0 28 | Linux 29 | 30 | ---------------------------------------------------------- 31 | ``` 32 | - passtool 指定key加解密 33 | 34 | ``` 35 | [root@andesli.com /project/go/src/gossh]#passtool -key="sos123" -e tata 36 | a12AAcm9PaUmIppJvq7fFw== 37 | [root@andesli.com /project/go/src/gossh]#passtool -key="sos123" -d a12AAcm9PaUmIppJvq7fFw== 38 | tata 39 | 40 | ``` 41 | 42 | gossh也支持密码插件功能,可以很方便的将加密后的密码存放到数据库中,详情见[gossh通过db获取密码环境搭建过程](https://github.com/andesli/gossh/blob/master/docs/use_mysql_db.md) 43 | 44 | ## 3.文件传递安全性 45 | 46 | ### 3.1 push文件到远程 47 | 48 | 1.会检测本地文件是否存在,不存在报错。 49 | 50 | ``` 51 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t push -f /project/go/src/gossh/ss /tmp 52 | ip=192.168.56.2 53 | command=push /project/go/src/gossh/ss to 192.168.56.2:/tmp 54 | return=1 55 | stat /project/go/src/gossh/ss: no such file or directory 56 | ---------------------------------------------------------- 57 | ``` 58 | 2.会检查远程目录是否存在,如果不存在报错。 59 | 60 | ``` 61 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t push -f /project/go/src/gossh/gossh.go /tata 62 | ip=192.168.56.2 63 | command=push /project/go/src/gossh/gossh.go to 192.168.56.2:/tata 64 | return=1 65 | [192.168.56.2:/tata] does not exist or not a dir 66 | 67 | ---------------------------------------------------------- 68 | ``` 69 | 3.会检查远程目录是否存在同名文件,如果存在,默认报错,可以使用-f参数强制覆盖。 70 | 71 | ``` 72 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t push /project/go/src/gossh/gossh.go /tmp 73 | ip=192.168.56.2 74 | command=push /project/go/src/gossh/gossh.go to 192.168.56.2:/tmp 75 | return=1 76 | 77 | Remote Server's /tmp has the same file /project/go/src/gossh/gossh.go 78 | You can use `-f` option force to cover the remote file. 79 | 80 | 81 | ---------------------------------------------------------- 82 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t push -f /project/go/src/gossh/gossh.go /tmp 83 | ip=192.168.56.2 84 | command=push /project/go/src/gossh/gossh.go to 192.168.56.2:/tmp 85 | return=0 86 | push /project/go/src/gossh/gossh.go to 192.168.56.2:/tmp ok 87 | 88 | ---------------------------------------------------------- 89 | ``` 90 | ### 3.2 pull文件安全性 91 | 92 | 1.pull会检测远程拉取的文件是否存在,不存在会报错。 93 | 94 | ``` 95 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t pull /project/go/src/gossh/ss /tmp 96 | ip= 192.168.56.2 97 | command= scp root@192.168.56.2:/project/go/src/gossh/ss /tmp 98 | return=1 99 | Remote Server's /project/go/src/gossh/ss doesn't exist. 100 | 101 | ---------------------------------------------------------- 102 | ``` 103 | 104 | 2.pull会检测远程拉取的是否是文件,如果是目录会报错。 105 | 106 | ``` 107 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t pull /project/go/src/gossh/ /tmp 108 | ip= 192.168.56.2 109 | command= scp root@192.168.56.2:/project/go/src/gossh/ /tmp 110 | return=1 111 | Remote Server's /project/go/src/gossh/ is a directory ,not support. 112 | 113 | ---------------------------------------------------------- 114 | ``` 115 | 116 | 3.pull会检查指定的本地路径是否存在,如果不存在会自动创建。 117 | 118 | ``` 119 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t pull /project/go/src/gossh/gossh /tmp/tata 120 | ip= 192.168.56.2 121 | command= scp root@192.168.56.2:/project/go/src/gossh/gossh /tmp/tata 122 | return=0 123 | Pull from /project/go/src/gossh/gossh to /tmp/tata ok. 124 | ---------------------------------------------------------- 125 | ``` 126 | 127 | 4.pull会检查指定的本地路径是否存在,如果指定的不是目录而是文件也会报错。 128 | 129 | ``` 130 | [root@andesli.com /project/go/src/gossh]#./gossh -h 192.168.56.2 -t pull /project/go/src/gossh/gossh /tmp/gossh 131 | ip= 192.168.56.2 132 | command= scp root@192.168.56.2:/project/go/src/gossh/gossh /tmp/gossh 133 | return=1 134 | /tmp/gossh is a normal file ,not a dir 135 | ---------------------------------------------------------- 136 | ``` 137 | ## 4. 日志审计 138 | 139 | gossh 默认会将所有执行的命令记录到日志文件中,日志文件默认位于./log/gossh.log。可以通过-l 选项指定日志级别,可以通过-logpath指定日志位置。 具体点击[输出和日志](https://github.com/andesli/gossh/blob/master/docs/output_format.md)详细页面。 140 | 141 | 142 | -------------------------------------------------------------------------------- /docs/use_mysql_db.md: -------------------------------------------------------------------------------- 1 | # gossh通过db获取密码环境搭建过程 2 | 3 | gossh对于密码的获取非常灵活,除了最简单的从命令行参数和ip文件中指定外,gossh在内部还实现了一个插件,那就是将密码初始化到DB中,gossh自动从db获取密码进行执行。现在就来介绍下如何搭建这样一个环境。 4 | 5 | ## 1.搭建一个mysql环境。 6 | 7 | 这里不再详解,参见[install mysql](https://dev.mysql.com/doc/refman/5.7/en/installing.html). 8 | 9 | ## 2.初始化库表。 10 | 11 | ``` 12 | --create db and table 13 | create database if not exists cmdb; use cmdb; 14 | create table if not exists t_password_info ( 15 | hostName varchar(225) NOT NULL DEFAULT '' COMMENT '登录机器ip', 16 | userName varchar(225) NOT NULL DEFAULT ''COMMENT '登录机器用户名', 17 | curPSW varchar(225) NOT NULL DEFAULT '' COMMENT '登录机器当前密码', 18 | curPSWStatus int(11) NOT NULL DEFAULT 0 COMMENT '当前密码状态', 19 | expiredTime datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '密码过期时间', 20 | lastPSW1 varchar(225) NOT NULL DEFAULT '', 21 | lastPSW2 varchar(225) NOT NULL DEFAULT '', 22 | createTime datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 23 | modifyTime datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 24 | defaultPSW varchar(225) NOT NULL DEFAULT 'tmpPasword', 25 | flag int(11) NOT NULL DEFAULT '0', 26 | PRIMARY KEY (hostName,userName) 27 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 28 | 29 | ``` 30 | 31 | ## 3.创建gossh访问mysql的用户。 32 | 33 | 如果mysql不在gossh所在的机器,将localhost改为gossh部署的机器ip。 34 | 35 | ``` 36 | --create db user for gossh 37 | create user 'mysql_user'@'localhost' identified by 'mysql_pass'; 38 | grant select on cmdb.* to 'mysql_user'@'localhost'; 39 | 40 | ``` 41 | ## 4.初始化机器登录信息到db. 42 | 43 | 将要管理的机器元信息初始化到DB库表中,注意密码字段要使用passtool加密后的值。 44 | 45 | ``` 46 | --init server login information 47 | insert into t_password_info (hostName,userName,curPSW) values ('localhost','root','+ojuqnTp/hXWtEZSn5xE7w=='); 48 | insert into t_password_info (hostName,userName,curPSW) values ('192.168.56.1','root','+ojuqnTp/hXWtEZSn5xE7w=='); 49 | 50 | ``` 51 | 52 | ## 5.修改gossh中的db配置参数 53 | 54 | gossh代码中写死了连接db的配置(这一块未来考虑通过参数可以指定),如果访问db的IP、端口、用户名、密码和代码中不一致,需要根据实际情况修改,代码位置如下: 55 | 56 | ``` 57 | // gossh/auth/db/query.go 58 | 59 | 9 const ( 60 | 10 dbtype = "mysql" 61 | 11 ipport = "localhost:3306" 62 | 12 user = "mysql_user" 63 | 13 passwd = "mysql_pass" 64 | 14 dbname = "cmdb" 65 | 15 querysql = `select curPSW from t_password_info as A where A.hostName=? and A.userName= ? ` 66 | 16 ) 67 | 68 | ``` 69 | 70 | ## 6.编译一个二进制程序。 71 | 72 | 重新编译gossh程序。 73 | 74 | ``` 75 | #需要有go编译环境 76 | cd $GOPATH/src && git clone https://github.com/andesli/gossh.git 77 | cd gossh 78 | 79 | //gossh工具 80 | go build gossh.go 81 | ``` 82 | 至此gossh就可以通过访问db获取密码了,这只是一个简单的实现,密码表设计的很简单。密码表每个公司设计各不一样,可以根据实际情况做改造,也可以自己实现一个密码插件注册进去,将gossh接入特定的密码管理系统中。 83 | 84 | -------------------------------------------------------------------------------- /enc/aes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package enc 18 | 19 | //package main 20 | 21 | import ( 22 | "bytes" 23 | "crypto/aes" 24 | "crypto/cipher" 25 | "encoding/base64" 26 | "fmt" 27 | ) 28 | 29 | func main() { 30 | testAes() 31 | } 32 | 33 | func testAes() { 34 | // AES-128。key长度:16, 24, 32 bytes 对应 AES-128, AES-192, AES-256 35 | sourcekey := []byte("zbOXeOdSedMK34QilHPUHw==") 36 | //key := ZeroPadding(sourcekey, 16) 37 | //key := []byte("sfe023f_9fd&fwfl") 38 | key := sourcekey[:16] 39 | result, err := AesEncrypt([]byte("3XZalfo*JV"), key) 40 | if err != nil { 41 | panic(err) 42 | } 43 | fmt.Println("encryptData=", base64.StdEncoding.EncodeToString(result)) 44 | origData, err := AesDecrypt(result, key) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Println("origData=", string(origData)) 49 | } 50 | 51 | func AesEncEncode(origData, key []byte) (string, error) { 52 | 53 | si, err := AesEncrypt(origData, key) 54 | if err != nil { 55 | return "", err 56 | } 57 | return base64.StdEncoding.EncodeToString(si), nil 58 | } 59 | 60 | func AesEncrypt(origData, key []byte) ([]byte, error) { 61 | block, err := aes.NewCipher(key) 62 | if err != nil { 63 | return nil, err 64 | } 65 | blockSize := block.BlockSize() 66 | //origData = PKCS5Padding(origData, blockSize) 67 | origData = ZeroPadding(origData, block.BlockSize()) 68 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) 69 | crypted := make([]byte, len(origData)) 70 | // 根据CryptBlocks方法的说明,如下方式初始化crypted也可以 71 | // crypted := origData 72 | blockMode.CryptBlocks(crypted, origData) 73 | return crypted, nil 74 | } 75 | 76 | func AesDecEncode(encodeStr string, key []byte) ([]byte, error) { 77 | crypted, err := base64.StdEncoding.DecodeString(encodeStr) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return AesDecrypt(crypted, key) 82 | } 83 | 84 | func AesDecrypt(crypted, key []byte) ([]byte, error) { 85 | block, err := aes.NewCipher(key) 86 | if err != nil { 87 | return nil, err 88 | } 89 | blockSize := block.BlockSize() 90 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) 91 | origData := make([]byte, len(crypted)) 92 | // origData := crypted 93 | blockMode.CryptBlocks(origData, crypted) 94 | //origData = PKCS5UnPadding(origData) 95 | //fmt.Println("len=", len(origData)) 96 | //fmt.Println("origData=", string(origData)) 97 | //fmt.Println("origData2=", origData) 98 | origData = ZeroUnPadding(origData) 99 | return origData, nil 100 | } 101 | 102 | func ZeroPadding(ciphertext []byte, blockSize int) []byte { 103 | padding := blockSize - len(ciphertext)%blockSize 104 | padtext := bytes.Repeat([]byte{0}, padding) 105 | return append(ciphertext, padtext...) 106 | } 107 | 108 | func ZeroUnPadding(origData []byte) []byte { 109 | length := len(origData) 110 | //fmt.Println("length-befor=", length) 111 | for length > 0 { 112 | unpadding := int(origData[length-1]) 113 | if unpadding == 0 && length > 0 { 114 | length = length - 1 115 | } else { 116 | break 117 | } 118 | } 119 | //fmt.Println("length-after=", length) 120 | //return origData[:(length - unpadding)] 121 | if length == 0 { 122 | return origData[:1] 123 | } else { 124 | return origData[:length] 125 | } 126 | } 127 | 128 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte { 129 | padding := blockSize - len(ciphertext)%blockSize 130 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 131 | return append(ciphertext, padtext...) 132 | } 133 | 134 | func PKCS5UnPadding(origData []byte) []byte { 135 | length := len(origData) 136 | // 去掉最后一个字节 unpadding 次 137 | unpadding := int(origData[length-1]) 138 | return origData[:(length - unpadding)] 139 | } 140 | -------------------------------------------------------------------------------- /enc/aes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package enc 18 | 19 | //package main 20 | 21 | import ( 22 | "encoding/base64" 23 | "fmt" 24 | "testing" 25 | ) 26 | 27 | var ( 28 | sourcekey = []byte("zbOXeOdSedMK34QilHPUHw==") 29 | cleartext = "tata" 30 | ciphertext = "" 31 | ) 32 | 33 | func TestAesEncrypt(t *testing.T) { 34 | key := sourcekey[:16] 35 | result, err := AesEncrypt([]byte(cleartext), key) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | fmt.Println("encryptData=", base64.StdEncoding.EncodeToString(result)) 41 | origData, err := AesDecrypt(result, key) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if string(origData) != cleartext { 46 | t.Fatal("dec error") 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /enc/key.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package enc 18 | 19 | import ( 20 | "sync" 21 | ) 22 | 23 | //aes default key 24 | var ( 25 | Key = []byte("suckdaNaanddf394des239") 26 | mu = &sync.Mutex{} 27 | ) 28 | 29 | //key长度为16,多了截取[:16],少了补'0' 30 | func SetKey(s []byte) { 31 | mu.Lock() 32 | defer mu.Unlock() 33 | n := len(s) 34 | if n < 16 { 35 | t := 16 - n 36 | for t > 0 { 37 | s = append(s, '0') 38 | t-- 39 | } 40 | } else { 41 | s = s[:16] 42 | } 43 | //println(string(s)) 44 | copy(Key, s) 45 | } 46 | 47 | //获取key 48 | func GetKey() []byte { 49 | mu.Lock() 50 | defer mu.Unlock() 51 | return Key[:16] 52 | } 53 | -------------------------------------------------------------------------------- /enc/key_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package enc 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestSetKey(t *testing.T) { 24 | key := []byte("123456789") 25 | SetKey(key) 26 | k := GetKey() 27 | if len(k) != 16 { 28 | t.Logf("%s\n", string(k)) 29 | t.Fail() 30 | } 31 | 32 | if string(k) != "1234567890000000" { 33 | t.Logf("%s\n", string(k)) 34 | t.Fail() 35 | } 36 | 37 | key = []byte("1234567812345678") 38 | SetKey(key) 39 | k = GetKey() 40 | 41 | if len(k) != 16 { 42 | t.Logf("%s\n", string(k)) 43 | t.Fail() 44 | } 45 | 46 | if string(k) != "1234567812345678" { 47 | t.Logf("%s\n", string(k)) 48 | t.Fail() 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/andesli/gossh 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/astaxie/beego v1.12.2 7 | github.com/go-sql-driver/mysql v1.5.0 8 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 3 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 4 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= 8 | github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= 9 | github.com/astaxie/beego v1.12.2 h1:CajUexhSX5ONWDiSCpeQBNVfTzOtPb9e9d+3vuU5FuU= 10 | github.com/astaxie/beego v1.12.2/go.mod h1:TMcqhsbhN3UFpN+RCfysaxPAbrhox6QSS3NIAEp/uzE= 11 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= 12 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= 13 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 14 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 15 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 16 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 17 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= 18 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 19 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 20 | github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= 21 | github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= 22 | github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= 23 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= 24 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 27 | github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= 28 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 29 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 30 | github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= 31 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 32 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 33 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 34 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 35 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 36 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 37 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 38 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 39 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 40 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 41 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 42 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 44 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 45 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 46 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 47 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 48 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 49 | github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 50 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 51 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 52 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 53 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 54 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 55 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 56 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 57 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 58 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 59 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 60 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 61 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 62 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 63 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 64 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 65 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 66 | github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= 67 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 68 | github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 69 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 70 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 72 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 73 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 74 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 75 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 76 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 77 | github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= 78 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 79 | github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 80 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 81 | github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= 82 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 83 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 84 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 85 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 86 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 87 | github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 88 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 89 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 90 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 91 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 92 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 93 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 94 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 95 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 96 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= 97 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= 98 | github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= 99 | github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s= 100 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= 101 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 102 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 103 | github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= 104 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 105 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 106 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 107 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 108 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 109 | github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 110 | github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 111 | github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 112 | github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= 113 | github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= 114 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 115 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 116 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 117 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= 118 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 119 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 120 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 121 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 122 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 123 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 124 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 125 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 126 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 127 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 128 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 129 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 130 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 131 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 132 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 133 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 134 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 135 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 137 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= 139 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 141 | golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 142 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 143 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 144 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 145 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 146 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 147 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 148 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 149 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 150 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 151 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 152 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 153 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 154 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 155 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 156 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 157 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 158 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 159 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 160 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 161 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 162 | -------------------------------------------------------------------------------- /help/help.go: -------------------------------------------------------------------------------- 1 | package help 2 | 3 | const Help = ` gossh 4 | 5 | NAME 6 | gossh is a smart ssh tool.It is developed by Go,compiled into a separate binary without any dependencies. 7 | 8 | DESCRIPTION 9 | gossh can do the follow things: 10 | 1.runs cmd on the remote host. 11 | 2.push a local file or path to the remote host. 12 | 3.pull remote host file to local. 13 | 14 | USAGE 15 | 1.Single Mode 16 | remote-comand: 17 | gossh -t cmd -h host -P port(default 22) -u user(default root) -p passswrod [-f] command 18 | 19 | Files-transfer: 20 | 21 | gossh -t push -h host -P port(default 22) -u user(default root) -p passswrod [-f] localfile remotepath 22 | 23 | 24 | gossh -t pull -h host -P port(default 22) -u user(default root) -p passswrod [-f] remotefile localpath 25 | 26 | 2.Batch Mode 27 | Ssh-comand: 28 | gossh -t cmd -i ip_filename -P port(default 22) -u user(default root) -p passswrod [-f] command 29 | 30 | Files-transfer: 31 | gossh -t push -i ip_filename -P port(default 22) -u user(default root) -p passswrod [-f] localfile remotepath 32 | gosh -t pull -i ip_filename -P port(default 22) -u user(default root) -p passswrod [-f] remotefile localpath 33 | 34 | EMAIL 35 | email.tata@qq.com 36 | ` 37 | -------------------------------------------------------------------------------- /logs/wraplog.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "github.com/astaxie/beego/logs" 5 | ) 6 | 7 | // RFC5424 log message levels. 8 | const ( 9 | LevelEmergency = iota 10 | LevelAlert 11 | LevelCritical 12 | LevelError 13 | LevelWarning 14 | LevelNotice 15 | LevelInformational 16 | LevelDebug 17 | ) 18 | 19 | // Legacy loglevel constants to ensure backwards compatibility. 20 | // 21 | // Deprecated: will be removed in 1.5.0. 22 | const ( 23 | LevelInfo = LevelInformational 24 | LevelTrace = LevelDebug 25 | LevelWarn = LevelWarning 26 | ) 27 | 28 | //declare only one log instance. all the app use it to write logs 29 | var ( 30 | log = logs.NewLogger(1000) 31 | ) 32 | 33 | //return a point to log instance for other package 34 | // all other packages in this app use this functions to get a log instance for writing logs 35 | func NewLogger() *logs.BeeLogger { 36 | return log 37 | } 38 | -------------------------------------------------------------------------------- /machine/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package machine 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "github.com/andesli/gossh/auth" 23 | _ "github.com/andesli/gossh/auth/db" 24 | "golang.org/x/crypto/ssh" 25 | //_ "github.com/andesli/gossh/auth/web" 26 | "github.com/andesli/gossh/logs" 27 | "github.com/andesli/gossh/scp" 28 | "github.com/andesli/gossh/tools" 29 | "io" 30 | "net" 31 | "os" 32 | "path" 33 | "path/filepath" 34 | "regexp" 35 | "strings" 36 | "time" 37 | ) 38 | 39 | var ( 40 | PASSWORD_SOURCE = "db" 41 | //PASSWORD_SOURCE = "web" 42 | 43 | NO_PASSWORD = "GET PASSWORD ERROR\n" 44 | 45 | log = logs.NewLogger() 46 | ) 47 | 48 | const ( 49 | NO_EXIST = "0" 50 | IS_FILE = "1" 51 | IS_DIR = "2" 52 | ) 53 | 54 | type Server struct { 55 | Ip string 56 | Port string 57 | User string 58 | Psw string 59 | Action string 60 | Cmd string 61 | FileName string 62 | RemotePath string 63 | Force bool 64 | Timeout int 65 | } 66 | 67 | type ScpConfig struct { 68 | Src string 69 | Dst string 70 | } 71 | 72 | type Result struct { 73 | Ip string 74 | Cmd string 75 | Result string 76 | Err error 77 | } 78 | 79 | func NewCmdServer(ip, port, user, psw, action, cmd string, force bool, timeout int) *Server { 80 | server := &Server{ 81 | Ip: ip, 82 | Port: port, 83 | User: user, 84 | Action: action, 85 | Cmd: cmd, 86 | Psw: psw, 87 | Force: force, 88 | Timeout: timeout, 89 | } 90 | if psw == "" { 91 | server.SetPsw() 92 | //log.Debug("server.Psw=%s", server.Psw) 93 | } 94 | return server 95 | } 96 | 97 | func NewScpServer(ip, port, user, psw, action, file, rpath string, force bool, timeout int) *Server { 98 | rfile := path.Join(rpath, path.Base(file)) 99 | cmd := createShell(rfile) 100 | server := &Server{ 101 | Ip: ip, 102 | Port: port, 103 | User: user, 104 | Psw: psw, 105 | Action: action, 106 | FileName: file, 107 | RemotePath: rpath, 108 | Cmd: cmd, 109 | Force: force, 110 | Timeout: timeout, 111 | } 112 | if psw == "" { 113 | server.SetPsw() 114 | } 115 | return server 116 | } 117 | func NewPullServer(ip, port, user, psw, action, file, rpath string, force bool) *Server { 118 | cmd := createShell(rpath) 119 | server := &Server{ 120 | Ip: ip, 121 | Port: port, 122 | User: user, 123 | Psw: psw, 124 | Action: action, 125 | FileName: file, 126 | RemotePath: rpath, 127 | Cmd: cmd, 128 | Force: force, 129 | } 130 | if psw == "" { 131 | server.SetPsw() 132 | } 133 | return server 134 | } 135 | 136 | /* 137 | func NewScp(src, dst string) ScpConfig { 138 | scp := ScpConfig{ 139 | Src: src, 140 | Dst: dst, 141 | } 142 | return scp 143 | } 144 | */ 145 | 146 | //query password from password plugin 147 | //PASSWORD_SOURCE: db|web 148 | func (server *Server) SetPsw() { 149 | psw, err := auth.GetPassword(PASSWORD_SOURCE, server.Ip, server.User) 150 | if err != nil { 151 | server.Psw = NO_PASSWORD 152 | return 153 | } 154 | server.Psw = psw 155 | } 156 | 157 | //run command for parallel 158 | func (server *Server) PRunCmd(crs chan Result) { 159 | rs := server.SRunCmd() 160 | crs <- rs 161 | } 162 | 163 | // set Server.Cmd 164 | func (s *Server) SetCmd(cmd string) { 165 | s.Cmd = cmd 166 | } 167 | 168 | //run command in sequence 169 | func (server *Server) RunCmd() (result string, err error) { 170 | if server.Psw == NO_PASSWORD { 171 | return NO_PASSWORD, nil 172 | } 173 | client, err := server.getSshClient() 174 | if err != nil { 175 | return "getSSHClient error", err 176 | } 177 | defer client.Close() 178 | 179 | session, err := client.NewSession() 180 | if err != nil { 181 | return "newSession error", err 182 | } 183 | defer session.Close() 184 | 185 | cmd := server.Cmd 186 | bs, err := session.CombinedOutput(cmd) 187 | if err != nil { 188 | return string(bs), err 189 | } 190 | return string(bs), nil 191 | } 192 | 193 | //run command in sequence 194 | func (server *Server) SRunCmd() Result { 195 | rs := Result{ 196 | Ip: server.Ip, 197 | Cmd: server.Cmd, 198 | } 199 | 200 | if server.Psw == NO_PASSWORD { 201 | rs.Err = errors.New(NO_PASSWORD) 202 | return rs 203 | } 204 | 205 | client, err := server.getSshClient() 206 | if err != nil { 207 | rs.Err = err 208 | return rs 209 | } 210 | defer client.Close() 211 | 212 | session, err := client.NewSession() 213 | if err != nil { 214 | rs.Err = err 215 | return rs 216 | } 217 | defer session.Close() 218 | 219 | cmd := server.Cmd 220 | bs, err := session.CombinedOutput(cmd) 221 | if err != nil { 222 | rs.Err = err 223 | return rs 224 | } 225 | rs.Result = string(bs) 226 | return rs 227 | } 228 | 229 | //execute a single command on remote server 230 | func (server *Server) checkRemoteFile() (result string) { 231 | re, _ := server.RunCmd() 232 | return re 233 | } 234 | 235 | //PRunScp() can transport file or path to remote host 236 | func (server *Server) PRunScp(crs chan Result) { 237 | cmd := "push " + server.FileName + " to " + server.Ip + ":" + server.RemotePath 238 | rs := Result{ 239 | Ip: server.Ip, 240 | Cmd: cmd, 241 | } 242 | result := server.RunScpDir() 243 | if result != nil { 244 | rs.Err = result 245 | } else { 246 | rs.Result = cmd + " ok\n" 247 | } 248 | crs <- rs 249 | } 250 | 251 | func (server *Server) RunScpDir() (err error) { 252 | re := strings.TrimSpace(server.checkRemoteFile()) 253 | log.Debug("server.checkRemoteFile()=%s\n", re) 254 | 255 | //远程机器存在同名文件 256 | if re == IS_FILE && server.Force == false { 257 | errString := "\nRemote Server's " + server.RemotePath + " has the same file " + server.FileName + "\nYou can use `-f` option force to cover the remote file.\n\n" 258 | return errors.New(errString) 259 | } 260 | 261 | rfile := server.RemotePath 262 | cmd := createShell(rfile) 263 | server.SetCmd(cmd) 264 | re = strings.TrimSpace(server.checkRemoteFile()) 265 | log.Debug("server.checkRemoteFile()=%s\n", re) 266 | 267 | //远程目录不存在 268 | if re != IS_DIR { 269 | errString := "[" + server.Ip + ":" + server.RemotePath + "] does not exist or not a dir\n" 270 | return errors.New(errString) 271 | } 272 | 273 | client, err := server.getSshClient() 274 | if err != nil { 275 | return err 276 | } 277 | defer client.Close() 278 | 279 | filename := server.FileName 280 | fi, err := os.Stat(filename) 281 | if err != nil { 282 | log.Debug("open source file %s error\n", filename) 283 | return err 284 | } 285 | scp := scp.NewScp(client) 286 | if fi.IsDir() { 287 | err = scp.PushDir(filename, server.RemotePath) 288 | return err 289 | } 290 | err = scp.PushFile(filename, server.RemotePath) 291 | return err 292 | } 293 | 294 | //pull file from remote to local server 295 | func (server *Server) PullScp() (err error) { 296 | 297 | //判断远程源文件情况 298 | re := strings.TrimSpace(server.checkRemoteFile()) 299 | log.Debug("server.checkRemoteFile()=%s\n", re) 300 | 301 | //不存在报错 302 | if re == NO_EXIST { 303 | errString := "Remote Server's " + server.RemotePath + " doesn't exist.\n" 304 | return errors.New(errString) 305 | } 306 | 307 | //不支持拉取目录 308 | if re == IS_DIR { 309 | errString := "Remote Server's " + server.RemotePath + " is a directory ,not support.\n" 310 | return errors.New(errString) 311 | } 312 | 313 | //仅仅支持普通文件 314 | if re != IS_FILE { 315 | errString := "Get info from Remote Server's " + server.RemotePath + " error.\n" 316 | return errors.New(errString) 317 | } 318 | 319 | //本地目录 320 | dst := server.FileName 321 | //远程文件 322 | src := server.RemotePath 323 | 324 | log.Debug("src=%s", src) 325 | log.Debug("dst=%s", dst) 326 | 327 | //本地路径不存在,自动创建 328 | err = tools.MakePath(dst) 329 | if err != nil { 330 | return err 331 | } 332 | 333 | //检查本地是否有同名文件 334 | fileName := filepath.Base(dst) 335 | localFile := filepath.Join(dst, fileName) 336 | 337 | flag := tools.FileExists(localFile) 338 | log.Debug("flag=%v", flag) 339 | log.Debug("localFile=%s", localFile) 340 | 341 | //-f 可以强制覆盖 342 | if flag && !server.Force { 343 | return errors.New(localFile + " is exist, use -f to cover the old file") 344 | } 345 | 346 | //执行pull 347 | client, err := server.getSshClient() 348 | if err != nil { 349 | return err 350 | } 351 | defer client.Close() 352 | 353 | scp := scp.NewScp(client) 354 | err = scp.PullFile(dst, src) 355 | return err 356 | } 357 | 358 | //RunScp1() only can transport file to remote host 359 | func (server *Server) RunScpFile() (result string, err error) { 360 | client, err := server.getSshClient() 361 | if err != nil { 362 | return "GetSSHClient Error\n", err 363 | } 364 | defer client.Close() 365 | 366 | filename := server.FileName 367 | session, err := client.NewSession() 368 | if err != nil { 369 | return "Create SSHSession Error", err 370 | } 371 | defer session.Close() 372 | 373 | go func() { 374 | Buf := make([]byte, 1024) 375 | w, _ := session.StdinPipe() 376 | defer w.Close() 377 | //File, err := os.Open(filepath.Abs(filename)) 378 | File, err := os.Open(filename) 379 | if err != nil { 380 | log.Debug("open scp source file %s error\n", filename) 381 | return 382 | } 383 | defer File.Close() 384 | 385 | info, _ := File.Stat() 386 | newname := filepath.Base(filename) 387 | fmt.Fprintln(w, "C0644", info.Size(), newname) 388 | for { 389 | n, err := File.Read(Buf) 390 | fmt.Fprint(w, string(Buf[:n])) 391 | if err != nil { 392 | if err == io.EOF { 393 | // transfer end with \x00 394 | fmt.Fprint(w, "\x00") 395 | return 396 | } else { 397 | fmt.Println("read scp source file error") 398 | return 399 | } 400 | } 401 | } 402 | }() 403 | 404 | cmd := "/usr/bin/scp -qt " + server.RemotePath 405 | bs, err := session.CombinedOutput(cmd) 406 | if err != nil { 407 | return string(bs), err 408 | } 409 | return string(bs), nil 410 | } 411 | 412 | // implement ssh auth method [password keyboard-interactive] and [password] 413 | func (server *Server) getSshClient() (client *ssh.Client, err error) { 414 | authMethods := []ssh.AuthMethod{} 415 | keyboardInteractiveChallenge := func( 416 | user, 417 | instruction string, 418 | questions []string, 419 | echos []bool, 420 | ) (answers []string, err error) { 421 | 422 | if len(questions) == 0 { 423 | return []string{}, nil 424 | } 425 | /* 426 | for i, question := range questions { 427 | log.Debug("SSH Question %d: %s", i+1, question) 428 | } 429 | */ 430 | 431 | answers = make([]string, len(questions)) 432 | for i := range questions { 433 | yes, _ := regexp.MatchString("*yes*", questions[i]) 434 | if yes { 435 | answers[i] = "yes" 436 | 437 | } else { 438 | answers[i] = server.Psw 439 | } 440 | } 441 | return answers, nil 442 | } 443 | authMethods = append(authMethods, ssh.KeyboardInteractive(keyboardInteractiveChallenge)) 444 | authMethods = append(authMethods, ssh.Password(server.Psw)) 445 | 446 | sshConfig := &ssh.ClientConfig{ 447 | User: server.User, 448 | Auth: authMethods, 449 | HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { 450 | return nil 451 | }, 452 | Timeout: (time.Duration(server.Timeout)) * time.Second, 453 | } 454 | //psw := []ssh.AuthMethod{ssh.Password(server.Psw)} 455 | //Conf := ssh.ClientConfig{User: server.User, Auth: psw} 456 | ip_port := server.Ip + ":" + server.Port 457 | client, err = ssh.Dial("tcp", ip_port, sshConfig) 458 | return 459 | } 460 | 461 | //create shell script for running on remote server 462 | func createShell(file string) string { 463 | s1 := "bash << EOF \n" 464 | s2 := "if [[ -f " + file + " ]];then \n" 465 | s3 := "echo '1'\n" 466 | s4 := "elif [[ -d " + file + " ]];then \n" 467 | s5 := `echo "2" 468 | else 469 | echo "0" 470 | fi 471 | EOF` 472 | cmd := s1 + s2 + s3 + s4 + s5 473 | return cmd 474 | } 475 | -------------------------------------------------------------------------------- /machine/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package machine 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func initServer() *Server { 24 | s := &Server{ 25 | Ip: "127.0.0.1", 26 | Port: "22", 27 | User: "root", 28 | Action: "cmd", 29 | Cmd: "uname", 30 | // password 31 | Psw: "hello@123", 32 | } 33 | 34 | return s 35 | } 36 | 37 | /* 38 | func TestSetPsw(t *testing.T) { 39 | s := initServer() 40 | s.SetPsw() 41 | if s.Psw != "NO_PASSWORD" { 42 | t.Error("get password fail") 43 | } 44 | } 45 | */ 46 | 47 | func TestRunCmd(t *testing.T) { 48 | s := initServer() 49 | // s.SetPsw() 50 | 51 | _, err := s.RunCmd() 52 | if err != nil { 53 | t.Fail() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /output/command.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package output 18 | 19 | import ( 20 | // "context" 21 | "fmt" 22 | "github.com/andesli/gossh/machine" 23 | // "strings" 24 | "sync" 25 | "time" 26 | ) 27 | 28 | const ( 29 | TIMEOUT = 4500 30 | ) 31 | 32 | //new print result 33 | func Print(res machine.Result) { 34 | fmt.Printf("ip=%s\n", res.Ip) 35 | //index := strings.Index(cmd, ";") 36 | //newcmd := cmd[index+1:] 37 | //fmt.Printf("ip=%s|command=%s\n", ip, cmd) 38 | fmt.Printf("command=%s\n", res.Cmd) 39 | if res.Err != nil { 40 | fmt.Printf("return=1\n") 41 | fmt.Printf("%s\n", res.Err) 42 | } else { 43 | fmt.Printf("return=0\n") 44 | fmt.Printf("%s\n", res.Result) 45 | } 46 | fmt.Println("----------------------------------------------------------") 47 | } 48 | 49 | func PrintResults2(crs chan machine.Result, ls int, wt *sync.WaitGroup, ccons chan struct{}, timeout int) { 50 | if timeout == 0 { 51 | timeout = TIMEOUT 52 | } 53 | 54 | for i := 0; i < ls; i++ { 55 | select { 56 | case rs := <-crs: 57 | //PrintResult(rs.Ip, rs.Cmd, rs.Result) 58 | Print(rs) 59 | case <-time.After(time.Second * time.Duration(timeout)): 60 | fmt.Printf("getSSHClient error,SSH-Read-TimeOut,Timeout=%ds", timeout) 61 | } 62 | wt.Done() 63 | <-ccons 64 | } 65 | 66 | } 67 | 68 | //print push file result 69 | func PrintPushResult(ip, src, dst string, err error) { 70 | fmt.Println("ip=", ip) 71 | fmt.Println("command=", "scp "+src+" root@"+ip+":"+dst) 72 | if err != nil { 73 | fmt.Printf("return=1\n") 74 | fmt.Println(err) 75 | } else { 76 | fmt.Printf("return=0\n") 77 | fmt.Printf("Push %s to %s ok.\n", src, dst) 78 | } 79 | fmt.Println("----------------------------------------------------------") 80 | } 81 | 82 | //print pull result 83 | func PrintPullResult(ip, src, dst string, err error) { 84 | fmt.Println("ip=", ip) 85 | fmt.Println("command=", "scp "+" root@"+ip+":"+dst+" "+src) 86 | if err != nil { 87 | fmt.Printf("return=1\n") 88 | fmt.Println(err) 89 | } else { 90 | fmt.Printf("return=0\n") 91 | fmt.Printf("Pull from %s to %s ok.\n", dst, src) 92 | } 93 | fmt.Println("----------------------------------------------------------") 94 | } 95 | -------------------------------------------------------------------------------- /output/json.go: -------------------------------------------------------------------------------- 1 | package output 2 | -------------------------------------------------------------------------------- /run/run.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/andesli/gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package run 18 | 19 | import ( 20 | "fmt" 21 | "github.com/andesli/gossh/machine" 22 | "github.com/andesli/gossh/output" 23 | // "net" 24 | // "strings" 25 | //"context" 26 | "errors" 27 | "github.com/andesli/gossh/config" 28 | "github.com/andesli/gossh/logs" 29 | // "github.com/andesli/gossh/tools" 30 | "path/filepath" 31 | "sync" 32 | ) 33 | 34 | var ( 35 | log = logs.NewLogger() 36 | ) 37 | 38 | type CommonUser struct { 39 | user string 40 | port string 41 | psw string 42 | force bool 43 | encflag bool 44 | } 45 | 46 | func NewUser(user, port, psw string, force, encflag bool) *CommonUser { 47 | return &CommonUser{ 48 | user: user, 49 | port: port, 50 | psw: psw, 51 | force: force, 52 | encflag: encflag, 53 | } 54 | 55 | } 56 | func SingleRun(host, cmd string, cu *CommonUser, force bool, timeout int) { 57 | server := machine.NewCmdServer(host, cu.port, cu.user, cu.psw, "cmd", cmd, force, timeout) 58 | r := server.SRunCmd() 59 | output.Print(r) 60 | } 61 | 62 | //func ServersRun(cmd string, cu *CommonUser, wt *sync.WaitGroup, crs chan machine.Result, ipFile string, ccons chan struct{}) { 63 | func ServersRun(cmd string, cu *CommonUser, wt *sync.WaitGroup, crs chan machine.Result, ipFile string, ccons chan struct{}, safe bool, timeout int) { 64 | hosts, err := parseIpfile(ipFile, cu) 65 | if err != nil { 66 | log.Error("Parse %s error, error=%s", ipFile, err) 67 | return 68 | } 69 | 70 | ips := config.GetIps(hosts) 71 | 72 | //config.PrintHosts(hosts) 73 | log.Info("[servers]=%v", ips) 74 | fmt.Printf("[servers]=%v\n", ips) 75 | 76 | ls := len(hosts) 77 | 78 | //ccons==1 串行执行,可以暂停 79 | if cap(ccons) == 1 { 80 | log.Debug("串行执行") 81 | for _, h := range hosts { 82 | server := machine.NewCmdServer(h.Ip, h.Port, h.User, h.Psw, "cmd", cmd, cu.force, timeout) 83 | r := server.SRunCmd() 84 | if r.Err != nil && safe { 85 | log.Debug("%s执行出错", h.Ip) 86 | output.Print(r) 87 | break 88 | } else { 89 | output.Print(r) 90 | } 91 | } 92 | } else { 93 | log.Debug("并行执行") 94 | go output.PrintResults2(crs, ls, wt, ccons, timeout) 95 | 96 | for _, h := range hosts { 97 | ccons <- struct{}{} 98 | server := machine.NewCmdServer(h.Ip, h.Port, h.User, h.Psw, "cmd", cmd, cu.force, timeout) 99 | wt.Add(1) 100 | go server.PRunCmd(crs) 101 | } 102 | } 103 | } 104 | 105 | func SinglePush(ip, src, dst string, cu *CommonUser, f bool, timeout int) { 106 | server := machine.NewScpServer(ip, cu.port, cu.user, cu.psw, "scp", src, dst, f, timeout) 107 | cmd := "push " + server.FileName + " to " + server.Ip + ":" + server.RemotePath 108 | 109 | rs := machine.Result{ 110 | Ip: server.Ip, 111 | Cmd: cmd, 112 | } 113 | err := server.RunScpDir() 114 | if err != nil { 115 | rs.Err = err 116 | } else { 117 | rs.Result = cmd + " ok\n" 118 | } 119 | output.Print(rs) 120 | } 121 | 122 | //push file or dir to remote servers 123 | func ServersPush(src, dst string, cu *CommonUser, ipFile string, wt *sync.WaitGroup, ccons chan struct{}, crs chan machine.Result, timeout int) { 124 | hosts, err := parseIpfile(ipFile, cu) 125 | if err != nil { 126 | log.Error("Parse %s error, error=%s", ipFile, err) 127 | return 128 | } 129 | 130 | ips := config.GetIps(hosts) 131 | log.Info("[servers]=%v", ips) 132 | fmt.Printf("[servers]=%v\n", ips) 133 | 134 | ls := len(hosts) 135 | go output.PrintResults2(crs, ls, wt, ccons, timeout) 136 | 137 | for _, h := range hosts { 138 | ccons <- struct{}{} 139 | server := machine.NewScpServer(h.Ip, h.Port, h.User, h.Psw, "scp", src, dst, cu.force, timeout) 140 | wt.Add(1) 141 | go server.PRunScp(crs) 142 | } 143 | } 144 | func SinglePull(host string, cu *CommonUser, src, dst string, force bool) { 145 | server := machine.NewPullServer(host, cu.port, cu.user, cu.psw, "scp", src, dst, force) 146 | err := server.PullScp() 147 | output.PrintPullResult(host, src, dst, err) 148 | } 149 | 150 | // pull romote server file to local 151 | func ServersPull(src, dst string, cu *CommonUser, ipFile string, force bool) { 152 | hosts, err := parseIpfile(ipFile, cu) 153 | if err != nil { 154 | log.Error("Parse %s error, error=%s", ipFile, err) 155 | return 156 | } 157 | ips := config.GetIps(hosts) 158 | log.Info("[servers]=%v", ips) 159 | fmt.Printf("[servers]=%v\n", ips) 160 | 161 | for _, h := range hosts { 162 | ip := h.Ip 163 | 164 | localPath := filepath.Join(src, ip) 165 | server := machine.NewPullServer(h.Ip, h.Port, h.User, h.Psw, "scp", localPath, dst, cu.force) 166 | err = server.PullScp() 167 | output.PrintPullResult(ip, localPath, dst, err) 168 | } 169 | } 170 | 171 | //common logic 172 | func parseIpfile(ipFile string, cu *CommonUser) ([]config.Host, error) { 173 | hosts, err := config.ParseIps(ipFile, cu.encflag) 174 | if err != nil { 175 | log.Error("Parse Ip File %s error,%s\n", ipFile, err) 176 | return hosts, err 177 | } 178 | 179 | if len(hosts) == 0 { 180 | return hosts, errors.New(ipFile + " is null") 181 | } 182 | hosts = config.PaddingHosts(hosts, cu.port, cu.user, cu.psw) 183 | return hosts, nil 184 | 185 | } 186 | -------------------------------------------------------------------------------- /run/run_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package run 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | var ( 24 | user = "root" 25 | port = "22" 26 | psw = "" 27 | cmd = "uname" 28 | ) 29 | 30 | func TestSingleRun(t *testing.T) { 31 | user := NewUser(user, port, psw, true, false) 32 | t.Log(user) 33 | } 34 | -------------------------------------------------------------------------------- /scp/README.md: -------------------------------------------------------------------------------- 1 | ## goscplib 2 | clone from https://github.com/gnicod/goscplib.git 3 | -------------------------------------------------------------------------------- /scp/scp.go: -------------------------------------------------------------------------------- 1 | package scp 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "golang.org/x/crypto/ssh" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | // the following code is a modified version of https://github.com/gnicod/goscplib 18 | // which follows https://blogs.oracle.com/janp/entry/how_the_scp_protocol_works 19 | //Constants 20 | 21 | const ( 22 | SCP_PUSH_BEGIN_FILE = "C" 23 | SCP_PUSH_BEGIN_FOLDER = "D" 24 | SCP_PUSH_BEGIN_END_FOLDER = "0" 25 | SCP_PUSH_END_FOLDER = "E" 26 | SCP_PUSH_END = "\x00" 27 | ) 28 | 29 | type Scp struct { 30 | client *ssh.Client 31 | } 32 | 33 | func GetPerm(f *os.File) (perm string) { 34 | fileStat, _ := f.Stat() 35 | mod := fileStat.Mode() 36 | // if it's a directory there's high bits we want to ditch 37 | // only keep the low bits 38 | if mod > (1 << 9) { 39 | mod = mod % (1 << 9) 40 | } 41 | return fmt.Sprintf("%#o", uint32(mod)) 42 | } 43 | 44 | //Initializer 45 | func NewScp(clientConn *ssh.Client) *Scp { 46 | return &Scp{ 47 | client: clientConn, 48 | } 49 | } 50 | 51 | //Pull file from remote to local 52 | //targetFile 远端目标文件 53 | //srcpath 本地目录 54 | func (scp *Scp) PullFile(srcpath, targetFile string) error { 55 | session, err := scp.client.NewSession() 56 | if err != nil { 57 | log.Fatalln("Failed to create session: " + err.Error()) 58 | return err 59 | } 60 | defer session.Close() 61 | 62 | go func() { 63 | iw, err := session.StdinPipe() 64 | if err != nil { 65 | log.Fatalln("Failed to create input pipe: " + err.Error()) 66 | } 67 | or, err := session.StdoutPipe() 68 | if err != nil { 69 | log.Fatalln("Failed to create output pipe: " + err.Error()) 70 | } 71 | fmt.Fprint(iw, "\x00") 72 | 73 | sr := bufio.NewReader(or) 74 | localFile := path.Join(srcpath, path.Base(targetFile)) 75 | src, srcErr := os.Create(localFile) 76 | if srcErr != nil { 77 | log.Fatalln("Failed to create source file: " + srcErr.Error()) 78 | } 79 | if controlString, ok := sr.ReadString('\n'); ok == nil && strings.HasPrefix(controlString, "C") { 80 | fmt.Fprint(iw, "\x00") 81 | controlParts := strings.Split(controlString, " ") 82 | size, _ := strconv.ParseInt(controlParts[1], 10, 64) 83 | /* 84 | bar := pb.New(int(size)) 85 | bar.Units = pb.U_BYTES 86 | bar.ShowSpeed = true 87 | bar.Start() 88 | rp := io.MultiReader(sr, bar) 89 | if n, ok := io.CopyN(src, rp, size); ok != nil || n < size { 90 | */ 91 | if n, ok := io.CopyN(src, sr, size); ok != nil || n < size { 92 | fmt.Fprint(iw, "\x02") 93 | return 94 | } 95 | // bar.Finish() 96 | sr.Read(make([]byte, 1)) 97 | } 98 | fmt.Fprint(iw, "\x00") 99 | }() 100 | 101 | if err := session.Run(fmt.Sprintf("scp -f %s", targetFile)); err != nil { 102 | log.Fatalln("Failed to run: " + err.Error()) 103 | return err 104 | } 105 | return nil 106 | } 107 | 108 | //Push one file to server 109 | func (scp *Scp) PushFile(src string, dest string) error { 110 | session, err := scp.client.NewSession() 111 | if err != nil { 112 | return err 113 | } 114 | defer session.Close() 115 | go func() { 116 | w, _ := session.StdinPipe() 117 | defer w.Close() 118 | fileSrc, srcErr := os.Open(src) 119 | defer fileSrc.Close() 120 | //fileStat, err := fileSrc.Stat() 121 | if srcErr != nil { 122 | log.Fatalln("Failed to open source file: " + srcErr.Error()) 123 | } 124 | //Get file size 125 | srcStat, statErr := fileSrc.Stat() 126 | if statErr != nil { 127 | log.Fatalln("Failed to stat file: " + statErr.Error()) 128 | } 129 | // Print the file content 130 | //fmt.Fprintln(w, SCP_PUSH_BEGIN_FILE+GetPerm(fileSrc), srcStat.Size(), filepath.Base(dest)) 131 | fmt.Fprintln(w, SCP_PUSH_BEGIN_FILE+GetPerm(fileSrc), srcStat.Size(), filepath.Base(src)) 132 | io.Copy(w, fileSrc) 133 | fmt.Fprint(w, SCP_PUSH_END) 134 | }() 135 | //if err := session.Run("/usr/bin/scp -rt " + filepath.Dir(dest)); err != nil { 136 | if err := session.Run("/usr/bin/scp -rt " + dest); err != nil { 137 | return err 138 | } 139 | return nil 140 | } 141 | 142 | //Push directory to server 143 | func (scp *Scp) PushDir(src string, dest string) error { 144 | session, err := scp.client.NewSession() 145 | if err != nil { 146 | return err 147 | } 148 | defer session.Close() 149 | go func() { 150 | w, _ := session.StdinPipe() 151 | //w = os.Stdout 152 | defer w.Close() 153 | folderSrc, _ := os.Open(src) 154 | fmt.Fprintln(w, SCP_PUSH_BEGIN_FOLDER+GetPerm(folderSrc), SCP_PUSH_BEGIN_END_FOLDER, filepath.Base(src)) 155 | lsDir(w, src) 156 | fmt.Fprintln(w, SCP_PUSH_END_FOLDER) 157 | 158 | }() 159 | if err := session.Run("/usr/bin/scp -qrt " + dest); err != nil { 160 | return err 161 | } 162 | return nil 163 | } 164 | 165 | func prepareFile(w io.WriteCloser, src string) { 166 | fileSrc, srcErr := os.Open(src) 167 | defer fileSrc.Close() 168 | if srcErr != nil { 169 | log.Fatalln("Failed to open source file: " + srcErr.Error()) 170 | } 171 | //Get file size 172 | srcStat, statErr := fileSrc.Stat() 173 | if statErr != nil { 174 | log.Fatalln("Failed to stat file: " + statErr.Error()) 175 | } 176 | // Print the file content 177 | fmt.Fprintln(w, SCP_PUSH_BEGIN_FILE+GetPerm(fileSrc), srcStat.Size(), filepath.Base(src)) 178 | io.Copy(w, fileSrc) 179 | fmt.Fprint(w, SCP_PUSH_END) 180 | } 181 | 182 | func lsDir(w io.WriteCloser, dir string) { 183 | fi, _ := ioutil.ReadDir(dir) 184 | //parcours des dossiers 185 | for _, f := range fi { 186 | if f.IsDir() { 187 | folderSrc, _ := os.Open(path.Join(dir, f.Name())) 188 | defer folderSrc.Close() 189 | fmt.Fprintln(w, SCP_PUSH_BEGIN_FOLDER+GetPerm(folderSrc), SCP_PUSH_BEGIN_END_FOLDER, f.Name()) 190 | lsDir(w, path.Join(dir, f.Name())) 191 | fmt.Fprintln(w, SCP_PUSH_END_FOLDER) 192 | } else { 193 | prepareFile(w, path.Join(dir, f.Name())) 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /sql/db_init.sql: -------------------------------------------------------------------------------- 1 | --create db and table 2 | create database if not exists cmdb; use cmdb; 3 | create table if not exists t_password_info ( 4 | hostName varchar(225) NOT NULL DEFAULT '' COMMENT '登录机器ip', 5 | userName varchar(225) NOT NULL DEFAULT ''COMMENT '登录机器用户名', 6 | curPSW varchar(225) NOT NULL DEFAULT '' COMMENT '登录机器当前密码', 7 | curPSWStatus int(11) NOT NULL DEFAULT 0 COMMENT '当前密码状态', 8 | expiredTime datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '密码过期时间', 9 | lastPSW1 varchar(225) NOT NULL DEFAULT '', 10 | lastPSW2 varchar(225) NOT NULL DEFAULT '', 11 | createTime datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 12 | modifyTime datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 13 | defaultPSW varchar(225) NOT NULL DEFAULT 'tmpPasword', 14 | flag int(11) NOT NULL DEFAULT '0', 15 | PRIMARY KEY (hostName,userName) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 17 | 18 | --create db user for gossh 19 | create user 'mysql_user'@'localhost' identified by 'mysql_pass'; 20 | grant select on cmdb.* to 'mysql_user'@'localhost'; 21 | 22 | --init server login information 23 | --注意密码必须使用passtool加密,插入的是加密后的值 24 | insert into t_password_info (hostName,userName,curPSW) values ('192.168.56.3','root','+ojuqnTp/hXWtEZSn5xE7w=='); 25 | insert into t_password_info (hostName,userName,curPSW) values ('192.168.56.2','root','+ojuqnTp/hXWtEZSn5xE7w=='); 26 | 27 | -------------------------------------------------------------------------------- /tools/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package tools 18 | 19 | import ( 20 | "errors" 21 | "os" 22 | "strings" 23 | ) 24 | 25 | //check the comand safe 26 | //true:safe false:refused 27 | func CheckSafe(cmd string, blacks []string) bool { 28 | lcmd := strings.ToLower(cmd) 29 | cmds := strings.Split(lcmd, " ") 30 | for _, ds := range cmds { 31 | for _, bk := range blacks { 32 | if ds == bk { 33 | return false 34 | } 35 | } 36 | } 37 | return true 38 | } 39 | 40 | //check path is exit 41 | 42 | func FileExists(path string) bool { 43 | fi, err := os.Stat(path) 44 | if err != nil { 45 | return false 46 | } else { 47 | return !fi.IsDir() 48 | } 49 | } 50 | 51 | func PathExists(path string) bool { 52 | fi, err := os.Stat(path) 53 | if err != nil { 54 | return false 55 | } else { 56 | return fi.IsDir() 57 | } 58 | } 59 | 60 | func MakePath(path string) error { 61 | if FileExists(path) { 62 | return errors.New(path + " is a normal file ,not a dir") 63 | } 64 | 65 | if !PathExists(path) { 66 | return os.MkdirAll(path, os.ModePerm) 67 | } else { 68 | return nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tools/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package tools 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | var ( 24 | blackList = []string{"rm", "mkfs", "mkfs.ext3", "make.ext2", "make.ext4", "make2fs", "shutdown", "reboot", "init", "dd"} 25 | cmds = []string{"rm -f /", "mkfs /dev/fioa", "shutdown now", "reboot"} 26 | files = []string{"/etc/fstab", "/etc/profile"} 27 | ) 28 | 29 | func TestCheckSafe(t *testing.T) { 30 | for _, cmd := range cmds { 31 | if CheckSafe(cmd, blackList) { 32 | t.Errorf("CheckSafe fail") 33 | } 34 | } 35 | 36 | } 37 | 38 | func TestFileExists(t *testing.T) { 39 | for _, f := range files { 40 | if !FileExists(f) { 41 | t.Errorf("FileExists fail") 42 | } 43 | } 44 | if FileExists("/xxxxx/xxx.txt") { 45 | t.Errorf("FileExists fail") 46 | } 47 | } 48 | 49 | func TestPathExists(t *testing.T) { 50 | if !PathExists("/home") { 51 | t.Errorf("PathExists Fail") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tools/hex/hex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gossh Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // Author: andes 15 | // Email: email.tata@qq.com 16 | 17 | package hex 18 | 19 | func HexStringToBytes(hex string) []byte { 20 | len := len(hex) / 2 21 | result := make([]byte, len) 22 | i := 0 23 | for i = 0; i < len; i++ { 24 | pos := i * 2 25 | result[i] = ToByte(hex[pos])<<4 | ToByte(hex[pos+1]) 26 | } 27 | return result 28 | } 29 | 30 | func ToByte(c uint8) byte { 31 | 32 | if c >= '0' && c <= '9' { 33 | return byte(c - '0') 34 | } 35 | if c >= 'a' && c <= 'z' { 36 | return byte(c - 'a' + 10) 37 | } 38 | if c >= 'A' && c <= 'Z' { 39 | return byte(c - 'A' + 10) 40 | } 41 | return 0 42 | } 43 | 44 | func BytesToHexString(data []byte) string { 45 | hex := []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} 46 | nLen := len(data) 47 | buff := make([]byte, 2*nLen) 48 | for i := 0; i < nLen; i++ { 49 | buff[2*i] = hex[(data[i]>>4)&0x0f] 50 | buff[2*i+1] = hex[data[i]&0x0f] 51 | } 52 | szHex := string(buff) 53 | return szHex 54 | } 55 | --------------------------------------------------------------------------------