【发布时间】:2018-01-01 19:11:21
【问题描述】:
我正在尝试通过 Go 使用 Go 的 golang.org/x/crypto/ssh 库通过 SSH 连接到 Cisco 无线控制器,以编程方式配置接入点。我遇到的问题是正确解析 Go 中的控制器 CLI。例如,这是到控制器的典型 SSH 登录:
$ ssh <controller_ip>
(Cisco Controller)
User: username
Password:****************
(Cisco Controller) >
我试图弄清楚如何在 Go 中建立 SSH 会话后发送用户名和密码。到目前为止,我能够成功地通过 SSH 连接到控制器,但程序在用户名提示下退出,如下所示:
$ go run main.go
(Cisco Controller)
User:
如何在提示时发送用户名,然后重复密码提示?
没有抛出错误或给出退出代码,所以我不确定为什么程序会在用户名提示下立即退出。但即使它没有以那种方式退出,我仍然不确定如何在控制器的 CLI 期待它时发送用户名和密码。
这是我的代码:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"io/ioutil"
"os"
"strings"
"path/filepath"
"bufio"
"fmt"
"errors"
"time"
)
const (
HOST = "host"
)
func main() {
hostKey, err := checkHostKey(HOST)
if err != nil {
log.Fatal(err)
}
key, err := ioutil.ReadFile("/Users/user/.ssh/id_rsa")
if err != nil {
log.Fatalf("unable to read private key: %v", err)
}
// Create the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("unable to parse private key: %v", err)
}
// Create client config
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
Timeout: time.Second * 5,
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", HOST+":22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
// Create a session
session, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := session.StdoutPipe()
if err != nil {
log.Fatal(err)
}
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.TTY_OP_ISPEED: 9600,
ssh.TTY_OP_OSPEED: 9600,
}
if err := session.RequestPty("xterm", 0, 200, modes); err != nil {
log.Fatal(err)
}
if err := session.Shell(); err != nil {
log.Fatal(err)
}
buf := make([]byte, 1000)
n, err := stdout.Read(buf) //this reads the ssh terminal welcome message
loadStr := ""
if err == nil {
loadStr = string(buf[:n])
}
for (err == nil) && (!strings.Contains(loadStr, "(Cisco Controller)")) {
n, err = stdout.Read(buf)
loadStr += string(buf[:n])
}
fmt.Println(loadStr)
if _, err := stdin.Write([]byte("show ap summary\r")); err != nil {
panic("Failed to run: " + err.Error())
}
}
func checkHostKey(host string) (ssh.PublicKey, error) {
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
return nil, errors.New(fmt.Sprintf("error parsing %q: %v", fields[2], err))
}
break
}
}
if hostKey == nil {
return nil, errors.New(fmt.Sprintf("no hostkey for %s", host))
}
return hostKey, nil
}
【问题讨论】:
-
我认为
user和password提示不是 ssh 身份验证的一部分,而是应用身份验证的一部分。试试ssh user@ip:port看看是否需要再次输入用户。 -
如果是这样的话,我想你可以试试
stdin.Write并在show ap summary之前写上你的用户名和密码。我很好奇你为什么选择\r作为换行符,我认为你应该用\n替换它。 -
对于输出。您的
fmt.Println(loadStr)打印了欢迎和提示,然后发送show ap summary,不需要任何其他操作。它自然退出。 -
完成认证部分后,您需要
session.Run来运行命令;std管道用于正在运行的程序,而不是外壳本身。 -
@leafbebop 在登录控制器时执行
ssh user@ip:port不会删除用户名或密码提示。输出看起来和我上面的一样。另外,我使用了 stdout 和 stderr 的管道,因为我最终需要能够发送多个命令,而我的印象是session.Run只能运行一次。
标签: go networking ssh network-programming cisco