How to create sftp client over exist ssh connection on golang - go

guys! I need help. I tried to create a new sftp connection over existing ssh client that i made few minutes ago. I can't. And idk why.
Here is a pie of code:
config := &ssh.ClientConfig{
User: *loginArg,
Auth: []ssh.AuthMethod{
ssh.Password(*passArg),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
addr := fmt.Sprintf("%s:%d", *ipArg, *portArg)
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
fmt.Printf("Failed to dial: %s", err)
}
fmt.Println("Successfully connected to ", *ipArg, ":", *portArg)
session, err := client.NewSession()
if err != nil {
fmt.Printf("Failed to create a new session: %s", err)
}
defer session.Close()
b, err := session.CombinedOutput("/system backup save name=BackUp dont-encrypt=yes") // /system backup save name=BackUp dont-encrypt=yes
if err != nil {
fmt.Printf("Failed to send output command: %s", err)
}
fmt.Print(string(b))
//*******************
var conn *ssh.Client
sftp, err := sftp.NewClient(conn)
//sftp, err := sftp.NewClient(conn) //err
if err != nil {
log.Fatal(err) //fmt.Printf("Failed to create new sftp-client: %s", err)
}
defer sftp.Close()

Find the answer: just need client instead of conn:
client, err := ssh.Dial("tcp", addr, config)
sftp, err := sftp.NewClient(client)

Related

Interact with a shell script running remotely via SSH

I am working an application written in golang,for which one of the capability will be to SSH into a device and execute a shell script there. With my current implementation,a normal script is getting executed.The problem lies in the execution of a script that requires user input: for e.g the script asks the user, can we proceed with the installation: and the user has to type in Y or N. This is the part where it is failing. Here is my implementation:
func main() {
cmdOutput,err := ExecuteScriptSSH()
if err != nil{
fmt.Printf("Error is %s",err)
}
fmt.Printf("Command Output is %s", cmdOutput)
}
func ExecuteScriptSSH() (string, error) {
script := "#!/bin/sh\n\n ls -l \n\n date"
params := make(map[string]interface{})
params["Username"] = "uname"
params["Password"] = "pwd"
params["IPAddress"] = "ip"
params["Port"] = "22"
connection := NewConnection(params)
client, err := connection.ConnectNonSecure()
if err != nil {
fmt.Printf("unable to Connect:%v ", err)
}
defer client.Close()
ss, err := client.NewSession()
if err != nil {
fmt.Printf("unable to create SSH session:%v ", err)
}
//Converting the Script to string to a shell script file by writing to it.
d1 := []byte(script)
err = os.WriteFile("/tmp/script.sh", d1, 0777)
if err != nil {
fmt.Printf("Error constructing Script file %v", err)
}
// opening the script file
scriptFile, err2 := os.OpenFile("/tmp/script.sh", os.O_RDWR|os.O_CREATE, 0777)
if err2 != nil {
fmt.Printf("Error opening Script file is %s", err)
}
cmdOutput := &bytes.Buffer{}
ss.Stdout = cmdOutput
ss.Stdin = scriptFile
interpreter := "sh"
err = ss.Run(interpreter)
if err != nil {
fmt.Printf("Error in Executing Script file %v", err.Error())
}
return cmdOutput.String(), err
}
type Params struct {
Username string
Password string
IPAddress string
Port string
}
func NewConnection(data map[string]interface{}) *Params {
var params Params
fmt.Printf("Data is %v",data)
err := mapstructure.Decode(data,&params)
if err != nil {
panic(err)
}
fmt.Printf("Mapped Connection details %v",params)
return &params
}
func (p *Params) ConnectNonSecure() (*ssh.Client, error){
config := &ssh.ClientConfig{
User: p.Username,
Auth: []ssh.AuthMethod{
ssh.Password(p.Password),
},
// Non-Production-only
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
fmt.Printf("Connection details %v",p)
// Connect to the remote server and perform a handshake
client, err := ssh.Dial("tcp", p.IPAddress+":"+p.Port, config)
if err != nil {
log.Fatalf("unable to connect to: %v %s", err,p.IPAddress)
}
return client, err
}
for simplicity sake I have added a very simple script,but actually the script is huge and requires user confirmation.Any idea on how to solve this issue?

Is the output from ssh guaranteed to be retrieved?

I use the ssh package to connect to a Linux server and retrieve the output of commands. The helper function I wrote for that is below:
func sshRunCommand(host string, command string) (output string, err error) {
keyData, err := ioutil.ReadFile("srv.private.openssh")
if err != nil {
return "", err
}
key, err := ssh.ParsePrivateKey(keyData)
if err != nil {
return "", err
}
// Authentication
config := &ssh.ClientConfig{
User: "root",
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
}
// Connect
client, err := ssh.Dial("tcp", net.JoinHostPort(host, "22"), config)
if err != nil {
return "", err
}
// Create a session. It is one session per command.
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
var b bytes.Buffer
session.Stdout = &b // 👈 this is the place I am concerned with
// Finally, run the command
err = session.Run(command)
return b.String(), err
}
This command usually works fine: it always connects but randomly does not return the output.
Before going further in my investigations, I wanted to make sure that the output buffer is flushed before returning the output. Is this the case?
In this use-case/issue, you can see stdout and stderr being linked to the same buffer.
See if that helps in your case.
sess, err := client.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer sess.Close()
stdin, err := sess.StdinPipe()
if err != nil {
log.Fatal(err)
}
var b bytes.Buffer
sess.Stdout = &b
sess.Stderr = &b
err = sess.Shell()
if err != nil {
log.Fatal(err)
}
...
_, err = fmt.Fprintf(stdin, "%s\n", cmd)

Operation timeout with SSH connection

I am trying to connect to a remote EC2-server through Go code using a PEM key provided by AWS. I am able to log in to the server through the command line using the PEM key.
I have done the following so far.
package main
import (
"io"
"io/ioutil"
"os"
"time"
"golang.org/x/crypto/ssh"
)
func publicKey(path string) ssh.AuthMethod {
key, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
panic(err)
}
return ssh.PublicKeys(signer)
}
func runCommand(cmd string, conn *ssh.Client) {
sess, err := conn.NewSession()
if err != nil {
panic(err)
}
defer sess.Close()
sessStdOut, err := sess.StdoutPipe()
if err != nil {
panic(err)
}
go io.Copy(os.Stdout, sessStdOut)
sessStderr, err := sess.StderrPipe()
if err != nil {
panic(err)
}
go io.Copy(os.Stderr, sessStderr)
err = sess.Run(cmd) // eg., /usr/bin/whoami
if err != nil {
panic(err)
}
}
func main() {
config := &ssh.ClientConfig{
User: "ec2-user",
Auth: []ssh.AuthMethod{
publicKey("mykey"),
},
Timeout: 15 * time.Second,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", "remote-server:22", config)
if err != nil {
panic(err.Error())
}
defer conn.Close()
runCommand("whoami", conn)
}
I keep getting the following error. What am I missing?
panic: ssh: handshake failed: read tcp "localhost"->"remotehost:22": read: operation timed out
In the above message I have replaced the IP addresses of the actual machines with bogus names.
I was able to resolve this since I was using an incorrect port to connect to :). I should be using port 22 for SSH, but was using some other service port.
Thanks.

Golang SSH to Cisco Wireless Controller and Run Commands

I am trying to SSH to a Cisco wireless controller through Go, using Go's golang.org/x/crypto/ssh library, to programmatically configure access points. The problem I'm running into is correctly parsing the controller CLI in Go. For example, this is the typical SSH login to the controller:
$ ssh <controller_ip>
(Cisco Controller)
User: username
Password:****************
(Cisco Controller) >
I am trying to figure out how to send the username and then the password after the SSH session is established in Go. So far, I am able to successfully SSH to the controller, but the program exits at the username prompt, like this:
$ go run main.go
(Cisco Controller)
User:
How would I go about sending the username when prompted, then repeating that for the password prompt?
No errors are being thrown or exit codes are being given, so I'm not sure why the program is exiting immediately at the username prompt. But Even if it wasn't exiting that way, I'm still unsure of how to send the username and password when the controller's CLI is expecting it.
Here is my code:
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
}
Finally got it working. Here is my new code inspired by this post:
package main
import (
"golang.org/x/crypto/ssh"
"log"
"io/ioutil"
"os"
"strings"
"path/filepath"
"bufio"
"fmt"
"errors"
"time"
)
func main() {
client, err := authenticate("10.4.112.11", "mwalto7", "lion$Tiger$Bear$")
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)
}
session.Stdout = os.Stdout
session.Stderr = os.Stderr
if err := session.Shell(); err != nil {
log.Fatal(err)
}
for _, cmd := range os.Args[1:] {
stdin.Write([]byte(cmd + "\n"))
}
stdin.Write([]byte("logout\n"))
stdin.Write([]byte("N\n"))
session.Wait()
}
func authenticate(host, username, password string) (ssh.Client, error) {
hostKey, err := checkHostKey(host)
if err != nil {
log.Fatal(err)
}
key, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".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)
return *client, err
}
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
}

SSH Handshake complains about missing host key

I'm trying to connect to a remote host and check if a file exist
At this stage I'm trying just to connect but I'm getting an error:
2017/08/01 18:16:39 unable to connect: ssh: handshake failed: ssh: required host key was nil
I've tried to find out if others had issues as mine but I just couldn't find.
I understand that I need to check the knowns_hosts somehow in the process but I just can't figure out how...
var hostKey ssh.PublicKey
// A public key may be used to authenticate against the remote
// server by using an unencrypted PEM-encoded private key file.
//
// If you have an encrypted private key, the crypto/x509 package
// can be used to decrypt it.
key, err := ioutil.ReadFile("/home/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)
}
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
// Connect to the remote server and perform the SSH handshake.
client, err := ssh.Dial("tcp", "host.com:22", config)
if err != nil {
log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
}
I would suggest to use knownhosts subpackage
import knownhosts "golang.org/x/crypto/ssh/knownhosts"
...
hostKeyCallback, err := knownhosts.New("/Users/user/.ssh/known_hosts")
if err != nil {
log.Fatal(err)
}
...
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{
// Use the PublicKeys method for remote authentication.
ssh.PublicKeys(signer),
},
HostKeyCallback: hostKeyCallback,
}
So that you avoid parsing known_hosts yourself...
hth,
Here what you are looking for:
func getHostKey(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) {
var err error
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
}
Then replace your hostKey definition line with
hostKey, err := getHostKey("host.com")
if err != nil {
log.Fatal(err)
}
For more information on the subject:
official sample where I took parts of the code from
why a hostKey is necessary now
EDIT:
Also check out Anton's answer below about the golang.org/x/crypto/ssh/knownhosts package.

Resources