I am writing a CLI for configuring Cisco access points through Cisco wireless controllers over SSH in Go 1.9.2. I am using the golang.org/x/crypto/ssh
package to handle connecting to the wireless controllers. I am able to successfully SSH to the desired controller, but I'm running into problems when attempting to send commands to the controller. I know the problem is due to the controller's prompt and output to Stdout. When manually connecting to a controller, this is the output:
$ ssh <controller_ip>
(Cisco Controller)
User: username
Password:****************
(Cisco Controller) >
Everything I have read and all the code examples I have seen of similar problems use the bufio
package to create a reader to deal with the prompts/output. I can get that to partially work, but the program hangs after printing the line (Cisco Controller)
before the username prompt, like this:
$ go run main.go
(Cisco Controller)
# infinite blinking cursor
I know this is because the controller is expecting a username to be passed.
My question, then, is how do I correctly handle parsing the output and send commands to the controller? I'm new to GO and network programming in general, so any help is appreciated. Here is my full code so far:
const (
HOST = "hostname"
)
func main() {
hostKey, err := HostKeyCheck(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()
// Error occurs after this point
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("show"); err != nil {
log.Fatalf("%v", err)
}
fmt.Println(b.String())
}
func HostKeyCheck(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
}
This is the output I get when running the program:
$ go run main.go
2017/12/28 14:13:28 ssh: command show failed
exit status 1