├── ExecFunctions.go
├── H3CcommandStruct.go
├── LICENSE
├── MuxShell.go
├── README.md
├── SwitchConfigApi.go
├── log
└── demo.log
├── server.sh
└── setanddel.json
/ExecFunctions.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | log "glog"
6 | "regexp"
7 | "strings"
8 | "time"
9 |
10 | "golang.org/x/crypto/ssh"
11 | )
12 |
13 | // //H3c6800Exec 尝试netconf操作
14 | // func H3c6800Exec(h3cStruct H3cCommand) int {
15 | // var CMDRETNUM = 200 //默认执行返回值200,表示正常
16 | //
17 | // // base64解密交换机密码
18 | // XXswitchRealPassword, _ := base64.URLEncoding.DecodeString(h3cStruct.SwitchPassword)
19 | // // 解密后的密码除去前10位之后的才是真正的密码
20 | // switchRealPassword := string(XXswitchRealPassword)[10:]
21 | //
22 | // //组装ssh的配置文件
23 | //
24 | // config := &ssh.ClientConfig{
25 | // User: h3cStruct.SwitchUsername,
26 | // Auth: []ssh.AuthMethod{
27 | // ssh.Password(switchRealPassword),
28 | // },
29 | // Timeout: h3cStruct.SwitchTimeout * time.Second,
30 | // Config: ssh.Config{
31 | // Ciphers: []string{"aes128-cbc"},
32 | // },
33 | // }
34 | // // config.Config.Ciphers = append(config.Config.Ciphers, "aes128-cbc")
35 | // clinet, err := ssh.Dial("tcp", h3cStruct.SwitchIPAndPort, config)
36 | // if err != nil {
37 | // log.Errorf(" ErrorCode:%d , %s", 303, err)
38 | // return 303
39 | // }
40 | //
41 | // session, err := clinet.NewSession()
42 | // defer session.Close()
43 | //
44 | // if err != nil {
45 | // log.Errorf(" ErrorCode:%d , %s", 304, err)
46 | // return 304
47 | // }
48 | //
49 | // modes := ssh.TerminalModes{
50 | // ssh.ECHO: 1, // disable echoing
51 | // ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
52 | // ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
53 | // }
54 | //
55 | // if err := session.RequestPty("vt100", 80, 40, modes); err != nil {
56 | //
57 | // log.Errorf(" ErrorCode:%d , %s", 305, err)
58 | // return 305
59 | // }
60 | //
61 | // w, err := session.StdinPipe()
62 | // if err != nil {
63 | //
64 | // log.Errorf(" ErrorCode:%d , %s", 306, err)
65 | // return 306
66 | // }
67 | // r, err := session.StdoutPipe()
68 | // if err != nil {
69 | // log.Errorf(" ErrorCode:%d , %s", 307, err)
70 | // return 307
71 | // }
72 | // e, err := session.StderrPipe()
73 | // if err != nil {
74 | // log.Errorf(" ErrorCode:%d , %s", 308, err)
75 | // return 308
76 | // }
77 | //
78 | // in, out := MuxShell(w, r, e)
79 | //
80 | // if err := session.Shell(); err != nil {
81 | // log.Errorf(" ErrorCode:%d , %s", 309, err)
82 | // return 309
83 | // }
84 | // <-out // 第一次输出为登陆后的设备提示信息, Copyright (c) 2004-2013 Hangzhou H3C Tech. Co., 可以不打印
85 | // in <- "xml"
86 | // //log.Infoln(<-out) //进入系统模式
87 | // fmt.Println(<-out)
88 | //
89 | // in <- `
90 | //
91 | //
92 | //
93 | // urn:ietf:params:netconf:base:1.0
94 | //
95 | //
96 | // ]]>]]>
97 | // `
98 | //
99 | // in <- `
100 | //
101 | //
102 | // ]]>]]>
103 | // `
104 | //
105 | // in <- "quit"
106 | //
107 | // for resultStr := range out { // 循环从out管道中遍历出最后两条的输出结果
108 | // log.Infoln(resultStr)
109 | // fmt.Println(<-out)
110 | // }
111 | //
112 | // session.Wait()
113 | // log.Infof("%s\n", "<--ExecClose")
114 | //
115 | // return CMDRETNUM
116 | //
117 | // }
118 |
119 | //H3cCommandViewRoute 本函数作用在于查看某条路由是否存在
120 | // func H3cCommandViewRoute(h3cStruct H3cCommand) ([]string, int) {
121 | //
122 | // var routeSlice []string //准备存放过滤出来的静态路由
123 | //
124 | // var CMDRETNUM = 200 //默认执行返回值0,
125 | //
126 | // // base64解密交换机密码
127 | // XXswitchRealPassword, _ := base64.URLEncoding.DecodeString(h3cStruct.SwitchPassword)
128 | // // 解密后的密码除去前10位之后的才是真正的密码
129 | // switchRealPassword := string(XXswitchRealPassword)[10:]
130 | //
131 | // //组装ssh的配置文件
132 | //
133 | // config := &ssh.ClientConfig{
134 | // User: h3cStruct.SwitchUsername,
135 | // Auth: []ssh.AuthMethod{
136 | // ssh.Password(switchRealPassword),
137 | // },
138 | // Timeout: h3cStruct.SwitchTimeout * time.Second,
139 | // Config: ssh.Config{
140 | // Ciphers: []string{"aes128-cbc"},
141 | // },
142 | // }
143 | // // config.Config.Ciphers = append(config.Config.Ciphers, "aes128-cbc")
144 | // clinet, err := ssh.Dial("tcp", h3cStruct.SwitchIPAndPort, config)
145 | // if err != nil {
146 | // log.Errorf(" ErrorCode:%d , %s", 303, err)
147 | // return routeSlice, 303
148 | // }
149 | // defer clinet.Close()
150 | //
151 | // session, err := clinet.NewSession()
152 | // if err != nil {
153 | // log.Errorf(" ErrorCode:%d , %s", 304, err)
154 | // return routeSlice, 304
155 | // }
156 | // defer session.Close()
157 | //
158 | //
159 | // modes := ssh.TerminalModes{
160 | // ssh.ECHO: 1, // disable echoing
161 | // ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
162 | // ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
163 | // }
164 | //
165 | // // 定义窗口
166 | // if err := session.RequestPty("vt100", 80, 40, modes); err != nil {
167 | //
168 | // log.Errorf(" ErrorCode:%d , %s", 305, err)
169 | // return routeSlice, 305
170 | // }
171 | //
172 | // w, err := session.StdinPipe()
173 | // if err != nil {
174 | // log.Errorf(" ErrorCode:%d , %s", 306, err)
175 | // return routeSlice, 306
176 | // }
177 | // r, err := session.StdoutPipe()
178 | // if err != nil {
179 | // log.Errorf(" ErrorCode:%d , %s", 307, err)
180 | // return routeSlice, 307
181 | // }
182 | // e, err := session.StderrPipe()
183 | // if err != nil {
184 | // log.Errorf(" ErrorCode:%d , %s", 308, err)
185 | // return routeSlice, 308
186 | // }
187 | //
188 | // in, out := MuxShell(w, r, e)
189 | //
190 | // if err := session.Shell(); err != nil {
191 | // log.Errorf(" ErrorCode:%d , %s", 309, err)
192 | // return routeSlice, 309
193 | // }
194 | // <-out // 第一次输出为登陆后的设备提示信息, Copyright (c) 2004-2013 Hangzhou H3C Tech. Co., 丢弃显示
195 | //
196 | // in <- h3cStruct.SwitchCommand
197 | //
198 | // in <- "quit" // 这个一输入就断开连接了, 得不到out的输出
199 | //
200 | // for resultStr := range out { // 循环从out管道中便利出执行结果
201 | // log.Infoln(resultStr)
202 | //
203 | // // 用于过滤的正则表达式也定制为只显示10.2XX类的路由,防止误伤到其他交换机路由。
204 | // re, _ := regexp.Compile("ip route-static 10\\.2[0-9]{2}\\.[0-9]{1,3}\\.[0-9]{1,3} .* [0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}")
205 | //
206 | // allinone := re.FindAll([]byte(resultStr), -1)
207 | //
208 | // for _, u := range allinone {
209 | // routeSlice = append(routeSlice, string(u)) // 填充到事先准备好的slice中
210 | //
211 | // }
212 | //
213 | // if strings.Contains(resultStr, "^") { //如果有别的错误类型也可以在这里添加返回码
214 | // CMDRETNUM = 400 // 命令不能识别
215 | // }
216 | //
217 | // }
218 | //
219 | // session.Wait()
220 | //
221 | // log.Infof("%s\n", "<--ViewClose")
222 | //
223 | // return routeSlice, CMDRETNUM
224 | //
225 | // }
226 |
227 | //H3cCommandExec 本函数作用执行一条添加路由的操作
228 | func H3cCommandExec(h3cStruct H3cCommand) int {
229 |
230 | var CMDRETNUM = 200 //默认执行返回值200,表示正常
231 |
232 | // base64解密交换机密码
233 | XXswitchRealPassword, _ := base64.URLEncoding.DecodeString(h3cStruct.SwitchPassword)
234 | // 解密后的密码除去前10位之后的才是真正的密码
235 | switchRealPassword := string(XXswitchRealPassword)
236 |
237 | //组装ssh的配置文件
238 |
239 | config := &ssh.ClientConfig{
240 | User: h3cStruct.SwitchUsername,
241 | Auth: []ssh.AuthMethod{
242 | ssh.Password(switchRealPassword),
243 | },
244 | Timeout: h3cStruct.SwitchTimeout * time.Second,
245 | Config: ssh.Config{
246 | Ciphers: []string{"aes128-cbc"},
247 | },
248 | }
249 | // config.Config.Ciphers = append(config.Config.Ciphers, "aes128-cbc")
250 | clinet, err := ssh.Dial("tcp", h3cStruct.SwitchIPAndPort, config)
251 | if err != nil {
252 | log.Errorf(" ErrorCode:%d , %s", 303, err)
253 | return 303
254 | }
255 | defer clinet.Close()
256 |
257 | session, err := clinet.NewSession()
258 |
259 | if err != nil {
260 | log.Errorf(" ErrorCode:%d , %s", 304, err)
261 | return 304
262 | }
263 | defer session.Close()
264 |
265 | modes := ssh.TerminalModes{
266 | ssh.ECHO: 1, // disable echoing
267 | ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
268 | ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
269 | }
270 |
271 | if err := session.RequestPty("vt100", 80, 40, modes); err != nil {
272 |
273 | log.Errorf(" ErrorCode:%d , %s", 305, err)
274 | return 305
275 | }
276 |
277 | w, err := session.StdinPipe()
278 | if err != nil {
279 |
280 | log.Errorf(" ErrorCode:%d , %s", 306, err)
281 | return 306
282 | }
283 | r, err := session.StdoutPipe()
284 | if err != nil {
285 | log.Errorf(" ErrorCode:%d , %s", 307, err)
286 | return 307
287 | }
288 | e, err := session.StderrPipe()
289 | if err != nil {
290 | log.Errorf(" ErrorCode:%d , %s", 308, err)
291 | return 308
292 | }
293 |
294 | in, out := MuxShell(w, r, e)
295 |
296 | if err := session.Shell(); err != nil {
297 | log.Errorf(" ErrorCode:%d , %s", 309, err)
298 | return 309
299 | }
300 | <-out // 第一次输出为登陆后的设备提示信息, Copyright (c) 2004-2013 Hangzhou H3C Tech. Co., 可以不打印
301 | in <- "sy"
302 | log.Infoln(<-out) //进入系统模式
303 |
304 | for _, _u := range strings.Split(h3cStruct.SwitchCommand, ";") {
305 |
306 | in <- _u //执行合规的那四种命令
307 | resultStr := <-out
308 | log.Infoln(resultStr)
309 |
310 | if strings.Contains(resultStr, "Route doesn't exist") {
311 | CMDRETNUM = 401 //操作的路由不存在
312 | }
313 | if strings.Contains(resultStr, "^") { //如果已经用正则表达式严谨的匹配过ip应该不会出现这里的错误
314 | CMDRETNUM = 400 // 命令不能识别
315 | }
316 |
317 | }
318 |
319 | //当执行的是多条命令时, 只有所有的命令都没有报错,才会执行save force ,只要有一条命令有问题,则不执行save动作。
320 | if CMDRETNUM == 200 { // 如果之前的命令都没有执行错误,则保存配置,否则不保存直接退出。<
321 | in <- "save force"
322 | }
323 |
324 | in <- "quit" //退出到普通用户模式。
325 | in <- "quit" // 这个一输入就断开连接了, 得不到out的输出
326 |
327 | for resultStr := range out { // 循环从out管道中遍历出最后两条的输出结果
328 | log.Infoln(resultStr)
329 | }
330 |
331 | session.Wait()
332 |
333 | log.Infof("%s\n", "<--ExecClose")
334 |
335 | return CMDRETNUM
336 |
337 | }
338 |
339 | func checkCmd(cmdStr string, cmdLevel int) bool { //是否包含以下这些掩码,包含的话说明威胁不大,如果不包含则不运行这条命令;
340 | InitStatus := true
341 |
342 | for i, _u := range strings.Split(cmdStr, ";") {
343 | // 如果命令行中有分号,则需要
344 |
345 | log.Infof("chechcmd %d:%s\n", i, _u)
346 |
347 | //如果行尾多加了一个分号
348 | if len(_u) < 10 {
349 | log.Warning("attention: len checkcmd < 10 ,too short")
350 | }
351 |
352 | //根据cmdlevel的不通,实施不同的匹配规则
353 | switch {
354 |
355 | case cmdLevel == 1:
356 | if m, _ := regexp.MatchString("^display .*", _u); m {
357 | //fmt.Println("---display----->", m, mm)
358 | continue
359 | }
360 | // 一组命令中只要有一条命令不合规,则返回false,此处不做详细的提示。
361 | log.Warning("checkcmd error," + _u)
362 | InitStatus = false
363 | break
364 |
365 | case cmdLevel == 2:
366 | if m, _ := regexp.MatchString("ip route-static 10\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3} 255\\.255\\.255\\.0 [0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}", _u); m { //操作的路由是10.2XX.X.X/24,以防止增删不当
367 | continue
368 | }
369 |
370 | // 一组命令中只要有一条命令不合规,则返回false,此处不做详细的提示。
371 | log.Warning("checkcmd error," + _u)
372 | InitStatus = false
373 | break
374 |
375 | default:
376 | log.Warning("checkcmd error, unknown cmdLevel")
377 | InitStatus = false
378 | }
379 |
380 | }
381 | //fmt.Println("for oever")
382 | return InitStatus
383 |
384 | }
385 |
--------------------------------------------------------------------------------
/H3CcommandStruct.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "time"
4 |
5 | //H3cCommand 用户发送来的json字符串
6 | //这个结构体同时被Server和client使用,如需更改结构体请同步。
7 | type H3cCommand struct {
8 | SwitchUsername string `json:"switch_username"`
9 | SwitchPassword string `json:"switch_password"`
10 | SwitchCmdLevel int `json:"switch_cmd_level"` // level1 的命令只用于display ; level 2 的命令是在sy模式下运行的
11 | SwitchCommand string `json:"switch_command"`
12 | SwitchIPAndPort string `json:"switch_ipandport"`
13 | SwitchTimeout time.Duration `json:"SwitchTimeout"` // second
14 | }
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/MuxShell.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | "sync"
8 | )
9 |
10 | //MuxShell 执行命令和输出结果的引擎,核心的执行动作函数
11 | func MuxShell(w io.Writer, r, e io.Reader) (chan<- string, <-chan string) {
12 | in := make(chan string, 3)
13 | out := make(chan string, 5)
14 | var wg sync.WaitGroup
15 | wg.Add(1) //for the shell itself ,登陆命令是第一条队列任务
16 |
17 | go func() {
18 | for cmd := range in { // 循环输入命令
19 | wg.Add(1) // 添加队列任务
20 | w.Write([]byte(cmd + "\n"))
21 | fmt.Printf("%s\n", cmd)
22 | // //由于netconf客户端回复hello的时候,server端没有任何回应,所以直接不用等待out
23 | // if strings.Contains(cmd, "]]>]]>") {
24 | // fmt.Println("contains")
25 | // out <- string("ccont")
26 | // wg.Done()
27 | // }
28 | wg.Wait() // 等待队列任务完成,执行完一条命令后,再执行下一条命令
29 | }
30 | }()
31 |
32 | go func() { // 进入死循环,一直读取数据;
33 | var (
34 | buf [128 * 1024]byte
35 | t int
36 | )
37 | for {
38 | n, err := r.Read(buf[t:])
39 |
40 | if err != nil {
41 | fmt.Println(err.Error())
42 | close(in)
43 | close(out)
44 | return
45 | }
46 | t += n
47 |
48 | result := string(buf[:t])
49 |
50 | if strings.Contains(result, "- More -") { // 遇到超常输出,需要加空格; 这种空格不会影响数据的完整性,亲测dis arp; dis mac-address ;
51 | w.Write([]byte(" "))
52 | }
53 |
54 | if strings.Contains(result, "name:") || strings.Contains(result, "word:") ||
55 | strings.Contains(result, ">") || strings.Contains(result, "]") || strings.Contains(result, "]]>]]>") {
56 | out <- string(buf[:t])
57 | t = 0
58 |
59 | wg.Done() //读到头一次,完成一个队列任务;
60 | }
61 | }
62 | }()
63 | return in, out
64 | }
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SwitchConfigApi
2 | ---------------------
3 |
4 | 简介
5 | -----------
6 | 1. 本程序为H3C交换机封装了一层北向接口, 用户可以通过RESTful的方式向接口提交JSON, 本程序会解析JSON内容,然后模拟登录交换机执行相应的命令。
7 | 2. 本程序只演示向交换机写入和删除路由的操作。
8 | 3. 已测试的设备有H3C S5120-48C-HI ,H3C S5500 ,H3C S6800-4C
9 | 4. 更多参数设置查看帮助: SwitchConfigApi --help
10 |
11 |
12 | Fire Up
13 | ---------------
14 | ```
15 | 启动接口服务
16 | ./server.sh
17 | ListenPort:0.0.0.0:8083,AuthKey:admin:admin2,ClientAuthKey:YWRh-WsuYWRh-Wsm,LimitListener:1 // 默认监听8083端口,默认客户端使用YWRh-WsuYWRh-Wsm进行http头验证,默认限制并发为1
18 |
19 |
20 | client 调用方法
21 | $ curl -d@'setanddel.json' http://127.0.0.1:8083 -H "Authorization: Basic YWRh-WsuYWRh-Wsm" //提交JSON
22 | 200
23 |
24 | ```
25 |
26 | client提交的JSON举例
27 | -----------------
28 | ```
29 |
30 | [setanddel.json]
31 | {
32 | "switch_username": "yihf", // ssh登录交换机所用的用户名
33 | "switch_password": "MTIzNDU2Nzg=", // 如果你的交换机密码为12345678,通过命令计算出base64转码的结果 : echo -n "12345678" | base64
34 | "switch_Cmd_level": 2, //选择2为执行命令,选择其它对应的处理函数会有不同可自行开发
35 | "switch_command": "ip route-static 10.201.88.0 255.255.255.0 10.10.88.129;undo ip route-static 10.201.88.0 255.255.255.0 10.10.88.129", //多条命令时,使用分号分割, 这里的举例是写入了一条路由又删了一条
36 | "switch_ipandport": "10.10.100.20:22", // 交换机IP+port
37 | "switch_timeout": 10 // 超时时间设置
38 | }
39 | ```
40 |
41 |
42 | 状态返回码
43 | --------------
44 | ```
45 | 1: 静态路由配置中包含查询的路由
46 | 0: 静态路由配置中不包含查询的路由
47 | 200: 命令没有报错。
48 | 400: 命令不识别
49 | 401: 操作的路由不存在
50 | 404 : body 或者认证头部不存在
51 | 511: 头部认证失败
52 | 300 : json串解析失败
53 | 301 : 命令不合规, 包含了level1 和level2 两种fail
54 | 302 : 未知的cmdlevel数值
55 | 303 : switch timeout , user or password fail!
56 | 304-309 : cmd_exec 函数执行过程中的错误。
57 | ```
58 |
59 |
60 | 保障“安全”的命令才能被执行
61 | ------------------------------
62 | ```
63 | 1. 为了确保只有安全的命令才会被提交,checkCmd函数有严格的正则表达式过滤.
64 |
65 | 2. 本例只允许两种命令可以被执行
66 | ip route-static 10.XXX.XXX.XXX 255.255.255.0 XXX.XXX.XXX.XXX 增加路由命令
67 | undo ip route-static 10.XXX.XXX.XXX 255.255.255.0 XXX.XXX.XXX.XXX 删除路由命令
68 | ```
69 |
70 |
71 | 安全机制
72 | ----------------
73 | 1. 客户端需要有合法的Http头验证,这个验证字符串是在本程序启动的时候生产,客户端必须使用这个特定字符串才能正确调用接口,可通过参数自定义头验证。
74 | 2. 客户端JSON中包含的交换机密码, 目前采用:base64(真实密码)加密, 如果你觉得不安全可以自行修改。
75 | 3. 服务端启动接口的时候可以定义并发数量,控制同一时间操作交换机的client数量。
76 | 4. 客户端提交的CLI命令,必须通过正则验证, 避免误操作。
77 |
78 |
79 |
80 | centos7创建服务
81 | -------------
82 | ```
83 | vi /usr/lib/systemd/system/hongswitchapi.service
84 | [Unit]
85 | Description=api of switch
86 | After=network.target
87 | After=network-online.target
88 | Wants=network-online.target
89 |
90 | [Service]
91 | Type=simple
92 | ExecStart=/home/workspace/src/SwitchConfigApi/SwitchConfigApi -log_dir=/home/workspace/src/SwitchConfigApi/log //程序目录请自行修改
93 | Restart=on-failure
94 |
95 | [Install]
96 | WantedBy=multi-user.target
97 | ```
98 |
99 |
100 | 启动服务并设置为开机启动
101 | ------------------
102 | ```
103 | systemctl start hongswitchapi.service
104 | systemctl enable hongswitchapi.service
105 | ```
106 |
107 |
108 | TODO
109 | -------------
110 | 1. 服务端可以对client的HOSTIP进行验证,防止非法ip调用。
111 |
112 |
113 |
114 | ## 开发环境
115 | golang 1.8
116 |
117 | ## 作者介绍
118 | yihongfei QQ:413999317
119 |
120 | CCIE 38649
121 |
122 |
123 | ## 寄语
124 | 为网络自动化运维尽绵薄之力,每一个网工都可以成为NetDevOps
125 |
--------------------------------------------------------------------------------
/SwitchConfigApi.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | "encoding/json"
6 | "flag"
7 | "fmt"
8 | log "glog"
9 | "io/ioutil"
10 | "net"
11 | "net/http"
12 | "os"
13 | "strings"
14 |
15 | limmit "golang.org/x/net/netutil"
16 | )
17 |
18 | const (
19 | //Base64Table 64个字符的顺序不同会产生不同的BASE64编码效果,起到轻量级的加密作用。
20 | Base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXY+-/0123456789abcdefghijklmnopqrstuvwxyz"
21 | )
22 |
23 | // AuthString 全局变量,用于存储server端用户自定义的key,通过全局变量传递进入handle函数。
24 | var AuthString string
25 |
26 | // HongCoder 自定义的base64编码表,可以作为一种简单的加密方法
27 | var HongCoder = base64.NewEncoding(Base64Table)
28 |
29 | func main() {
30 |
31 | // 参数部分使用flag包实现
32 | var InputIPPort = flag.String("IpPort", "0.0.0.0:8083", "本地监听的IP和端口")
33 | var InputAuthKey = flag.String("AuthKey", "admin:admin2", "客户端需要在http头中包含这个key的特殊编码作为认证,以证明是合法客户端请求") //可扩展为从中提取出用户角色信息,思路来源于docker仓库
34 | var InputHelp = flag.Bool("help", false, "help info")
35 | var InputVersion = flag.Bool("version", false, "version information")
36 | var InputLimit = flag.Int("Limit", 1, "同一时间访问该接口的client端的数量,越低对交换机cpu影响越小")
37 | //var InputLogDir = flag.String("log_dir", "", "log file dir")
38 | flag.Parse()
39 |
40 | //判断日志文件夹是否存在, 不存在则创建一个
41 | //fmt.Println(*InputLogDir)
42 |
43 | //fmt.Println(flag.NArg()) 以上指定的参数都不计数
44 | if (flag.NArg() > 0) || (*InputHelp) {
45 | fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]...\nExample:%s\n注意:log_dir指定的需要是已存在的目录。 \n\n", os.Args[0], " ./SwitchConfigApi -AuthKey=admin:admin2 -IpPort=0.0.0.0:8083 --log_dir=./log -alsologtostderr ")
46 | flag.PrintDefaults()
47 |
48 | os.Exit(0)
49 |
50 | }
51 |
52 | if *InputVersion { //如果用户是想看版本信息,则显示完就退出
53 | fmt.Fprintln(os.Stderr, "version 1.0 ,Copyright@hongfeio.o@163.com")
54 | os.Exit(0)
55 | }
56 |
57 | AuthString = *InputAuthKey //复制给全局变量,传入hander
58 |
59 | // 以下回显中包含,用户自定义的ip+端口,认证的Key,括弧内是特殊编码后的字符串,客户端需要把这段字符串包含在http头中进行API的验证, 还显示了http server的并发数限制。
60 | log.Infof("ListenPort:%s,AuthKey:%s(ClientAuthKey:%s),LimitListener:%d\n", *InputIPPort, *InputAuthKey, HongCoder.EncodeToString([]byte(*InputAuthKey)), *InputLimit)
61 | fmt.Printf("ListenPort:%s,AuthKey:%s,ClientAuthKey:%s,LimitListener:%d\n", *InputIPPort, *InputAuthKey, HongCoder.EncodeToString([]byte(*InputAuthKey)), *InputLimit)
62 |
63 | // http 监听函数
64 | l, err := net.Listen("tcp", *InputIPPort)
65 | if err != nil {
66 | log.Warning(err.Error())
67 |
68 | }
69 | defer l.Close()
70 | defer log.Flush() // 确保日志都写入文件中。
71 |
72 | l = limmit.LimitListener(l, *InputLimit) // 接口并行的数量默认限制为1,即同一个时间只有一个客户端可以访问接口,1 防止多个客户端同时操作一台交换机 ,2 保证日志的可读性
73 |
74 | http.HandleFunc("/", handler) //设置访问的路由
75 | http.Serve(l, nil) //这个函数是一个死循环, 一直监听客户端的请求。
76 |
77 | }
78 |
79 | //handler 主要函数
80 | func handler(w http.ResponseWriter, r *http.Request) {
81 |
82 | // 使手机端可以跨域请求
83 | w.Header().Add("Access-Control-Allow-Origin", "*")
84 | w.Header().Add("Access-Control-Allow-Headers", "Origin,Content-Type, Accept,User-Agent,Authorization,Accept-Encoding")
85 | defer r.Body.Close()
86 |
87 | // 只接受POST和GET方法, 因为手机端会发送出OPTIONS的请求,过滤掉
88 | if (r.Method == "POST") || (r.Method == "GET") {
89 |
90 | body, _ := ioutil.ReadAll(r.Body)
91 | bodyStr := string(body)
92 |
93 | // 日志显示客户端发起的请求信息
94 | log.Infof("<--begin\n Body:%s\n Content-Type:%s\n Authorization:%s\n Content-Length:%d\n User-Agent:%s\n Method:%s\n Host:%s\n", bodyStr, r.Header["Content-Type"], r.Header["Authorization"], r.ContentLength, r.UserAgent(), r.Method, r.Host)
95 |
96 | // 判断参数是否完整,包括json串和认证字符串,任意为空返回404
97 | if (bodyStr == "") || (r.Header["Authorization"] == nil) {
98 | log.Errorf(" ErrorCode:%d no json or no Authorization!\n", 404)
99 | fmt.Fprintf(w, "%d\n", 404)
100 | return
101 | }
102 |
103 | // 判断http中的认证是否正确
104 | AuthStr := r.Header["Authorization"]
105 | userpwdBase64encode := strings.Split(AuthStr[0], " ")[1]
106 | uDec, _ := HongCoder.DecodeString(userpwdBase64encode) //解密客户端提供上来的字符串
107 |
108 | log.Infof(" ServerAuthKey: %s VS ClientAuthKeyDecode: %s\n", AuthString, string(uDec)) //服务器端定义的AuthKey和客户端上报的AuthKey对比显示。
109 |
110 | if string(uDec) != AuthString { // AuthString是用户设定的参数, client端请求API的时候需要把这个字符串Hong_base64自定义运算,放入http的头部。
111 |
112 | log.Errorf(" ErrorCode:%d Authorization Fail!\n", 511)
113 | fmt.Fprintf(w, "%d\n", 511)
114 | return
115 |
116 | }
117 |
118 | // 开始解析json串
119 | var h3c H3cCommand
120 |
121 | if err := json.Unmarshal(body, &h3c); err != nil {
122 |
123 | log.Warningf(" WarningCode:%d Json Decode Fail! %s\n", 300, err)
124 | fmt.Fprintf(w, "%d\n", 300)
125 | return
126 | }
127 |
128 | // 根据不同的cmdlevel丢给不同的函数处理。
129 | switch {
130 | // case h3c.SwitchCmdLevel == 1:
131 | //
132 | // if !checkCmd(h3c.SwitchCommand, 1) {
133 | // log.Warningf(" WarningCode:%d CheckCmd level 1 Fail!\n", 301)
134 | // fmt.Fprintf(w, "%d\n", 301)
135 | // return
136 | // }
137 | // routeString, retnum := H3cCommandViewRoute(h3c)
138 | // if retnum != 200 {
139 | // log.Warningf(" WarningCode:%d H3cCommandViewRoute !\n", retnum)
140 | // fmt.Fprintf(w, "%d\n", retnum)
141 | // return
142 | // }
143 | // fmt.Fprintf(w, "%s\n", strings.Join(routeString, ";"))
144 | // return
145 |
146 | case h3c.SwitchCmdLevel == 2:
147 | // 判断命令是否合规的函数,合规返回true,不合规返回false
148 | if !checkCmd(h3c.SwitchCommand, 2) {
149 | log.Warningf(" WarningCode:%d CheckCmd level 2 Fail!\n", 301)
150 | fmt.Fprintf(w, "%d\n", 301)
151 | return
152 | }
153 | fmt.Fprintf(w, "%d\n", H3cCommandExec(h3c)) // 200表示正常执行,返回400表示命令有错误(^) ,返回值可以是一个json串,可以参考vservermap2的代码
154 | return
155 |
156 | default:
157 | log.Warningf(" WarningCode:%d cmd level is unknown!\n", 302)
158 | fmt.Fprintf(w, "%d\n", 302)
159 | return
160 |
161 | }
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/log/demo.log:
--------------------------------------------------------------------------------
1 | 这个目录存放日志
2 |
--------------------------------------------------------------------------------
/server.sh:
--------------------------------------------------------------------------------
1 |
2 | go build SwitchConfigApi.go MuxShell.go ExecFunctions.go H3CcommandStruct.go
3 | ./SwitchConfigApi -logtostderr
4 |
--------------------------------------------------------------------------------
/setanddel.json:
--------------------------------------------------------------------------------
1 | {
2 | "switch_username": "yihf",
3 | "switch_password": "MTIzNDU2Nzg=",
4 | "switch_Cmd_level": 2,
5 | "switch_command": "ip route-static 10.21.8.0 255.255.255.0 10.10.88.1;undo ip route-static 10.20.8.0 255.255.255.0 10.10.88.1",
6 | "switch_ipandport": "10.10.100.20:22",
7 | "switch_timeout": 10
8 | }
9 |
--------------------------------------------------------------------------------