Golang : Execute terminal command to remote machine example




Problem:

You need to query a remote machine with ssh to extract information such as memory usage or CPU information. Instead of opening a terminal and ssh to each individual remote machine, you want to write a Golang program that will execute a command and parse the output to your terminal or save to text files for processing.

How to do that?

Solution:

This is similar to Python's paramiko module that allow developers to create a remote session to a Unix/Linux machine. For Golang, we will use golang.org/x/crypto/ssh and golang.org/x/crypto/ssh/terminal packages to achieve similar result. In the code example below, we will connect to a remote Linux machine and execute the command cat /proc/cpuinfo to extract the CPU information. It can return static response from commands such as ps -ef or whoami, but not top command.

NOTE: If you're planning to query specific command only. Feel free to modify and hard code the command into the program instead of asking username/password input from the user. Make sure that the program has proper ownership and use proper mechanism to hide the plain password hard coded into the program.

Here you go!


 package main

 import (
 "bufio"
 "bytes"
 "fmt"
 "golang.org/x/crypto/ssh"
 "golang.org/x/crypto/ssh/terminal"
 "net"
 "os"
 "strings"
 )

 func GetCredential() (string, string) {
 reader := bufio.NewReader(os.Stdin)

 fmt.Print("Enter Username: ")
 username, _ := reader.ReadString('\n')

 fmt.Print("Enter Password: ")
 bytePassword, err := terminal.ReadPassword(0)
 if err != nil {
 panic(err)
 }
 password := string(bytePassword)

 return strings.TrimSpace(username), strings.TrimSpace(password)
 }

 func main() {
 if len(os.Args) != 3 {
 fmt.Printf("Usage : %s <hostname> <port> \n", os.Args[0])
 os.Exit(0)
 }

 hostname := os.Args[1]
 port := os.Args[2]

 username, password := GetCredential()
 config := &ssh.ClientConfig{
 User: username,
 Auth: []ssh.AuthMethod{ssh.Password(password)},
 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
 return nil
 },
 }
 fmt.Println("\nConnecting to ", hostname, port)

 hostaddress := strings.Join([]string{hostname, port}, ":")
 client, err := ssh.Dial("tcp", hostaddress, config)
 if err != nil {
 panic(err.Error())
 }

 for {
 session, err := client.NewSession()
 if err != nil {
 panic(err.Error())
 }
 defer session.Close()

 fmt.Println("To exit this program, hit Control-C")
 fmt.Printf("Enter command to execute on %s : ", hostname)

 // fmt.Scanf is unable to accept command with parameters
 // see solution at
 // https://www.socketloop.com/tutorials/golang-accept-input-from-user-with-fmt-scanf-skipped-white-spaces-and-how-to-fix-it
 //fmt.Scanf("%s", &cmd)

 commandReader := bufio.NewReader(os.Stdin)
 cmd, _ := commandReader.ReadString('\n')
 //log.Printf(cmd)
 fmt.Println("Executing command ", cmd)

 // capture standard output
 // will NOT be able to handle refreshing output such as TOP command
 // executing top command will result in panic
 var buff bytes.Buffer
 session.Stdout = &buff
 if err := session.Run(cmd); err != nil {
 panic(err.Error())
 }

 fmt.Println(buff.String())

 }
 }

Sample output after cat /proc/cpuinfo on a remote Linux machine:

 ./remoteQuery 1xx.xxx.xx.xx xxxx
 Enter Username: xxxxxxx
 Enter Password:
 Connecting to  1xx.xxx.xx.xx xxxx
 To exit this program, hit Control-C
 Enter command to execute on 1xx.xxx.xx.xx : cat /proc/cpuinfo
 Executing command  cat /proc/cpuinfo

 processor : 0
 vendor_id : GenuineIntel
 cpu family : 6
 model  : 62
 model name : Intel(R) Xeon(R) CPU E5-2630L v2 @ 2.40GHz
 stepping : 4
 microcode : 0x1
 cpu MHz  : 2399.998
 cache size : 15360 KB
 physical id : 0
 siblings : 1
 core id  : 0
 cpu cores : 1
 apicid  : 0
 initial apicid : 0
 fpu  : yes
 fpu_exception : yes
 cpuid level : 13
 wp  : yes
 flags  : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon rep_good nopl pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm vnmi ept fsgsbase tsc_adjust smep erms xsaveopt arat
 bugs  :
 bogomips : 4799.99
 clflush size : 64
 cache_alignment : 64
 address sizes : 40 bits physical, 48 bits virtual
 power management:

Happy coding!

References:

https://www.socketloop.com/tutorials/golang-accept-input-from-user-with-fmt-scanf-skipped-white-spaces-and-how-to-fix-it

https://github.com/Juniper/go-netconf/issues/27

https://stackoverflow.com/questions/23019890/golang-write-input-and-get-output-from-terminal-process

  See also : Golang : How to check if your program is running in a terminal





By Adam Ng

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement