how to increase golang.org/x/crypto/ssh verbosity - go

i wanted to use golang.org/x/crypto/ssh to write a little program which is able to read the equivalent to ssh -v output. While i am able to connect via the ssh package to my server i have no idea how to enable verbose output or get to the desired information in it. As i am new to golang i am not even able to decide if it is possible at all.
Here is my code so far
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
)
type SSHClient struct {
Config *ssh.ClientConfig
Host string
Port int
}
func main() {
sshConfig := &ssh.ClientConfig{
User: "your_user_name",
Auth: []ssh.AuthMethod{
ssh.Password("your_password"),
},
}
client := &SSHClient{
Config: sshConfig,
Host: "example.com",
Port: 22,
}
client.test()
_, err := ssh.Dial("tcp", "example.com:22", sshConfig)
if err != nil {
fmt.Printf("Failed to dial: %s", err)
}
}
func (client *SSHClient) test() {
fmt.Println("test")
}

As a quick hack you can open $GOPATH/golang.org/x/crypto/ssh/mux.go file, change const debugMux = false to const debugMux = true and recompile your program.
In order to see the debug logging make your program do something first:
s, err := c.NewSession()
if err != nil {
fmt.Printf("Failed to create session: %s", err)
}
fmt.Println(s.CombinedOutput("hostname"))

Related

Golang SSH client error "unable to authenticate, attempted methods [none publickey], no supported methods remain"

For some reason Golang SSH client can't connect to my EC2 instance. It throws the following error:
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain
This is my code:
package main
import (
"fmt"
"github.com/helloyi/go-sshclient"
)
func main() {
client, err := sshclient.DialWithKey("ip:port", "ubuntu", "my_key.pem")
if err != nil {
fmt.Println(err)
return
}
out, err := client.Cmd("help").Output()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(out))
}
What's interesting is that when I ran this code on my other computer the connection was made without any errors. So I think it must be a problem with the PC and not my code. I also tried connecting to the instance in Python using Paramiko client and it worked flawlessly. Of course I tried connecting using ssh command in CMD and MobaXTerm client - both worked. I tried using other Golang SSH client golang.org/x/crypto/ssh and it didn't work (same error).
Thank you for your help.
Apparently, it was an issue with go.mod file. Both golang.org/x/crypto and golang.org/x/sys were outdated, once I updated them it started working. Thanks #kkleejoe for your help.
assume you can ssh user#host without password, public key may be ~/.ssh/id_rsa or ~/.ssh/id_ecda
import "golang.org/x/crypto/ssh"
import "io/ioutil"
import "strconv"
func DialWithPublickey(addr string, port int, user, publickeyfile string) (*ssh.Client, error) {
key, err := ioutil.ReadFile(publickeyfile)
if err != nil {
return nil, err
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, err
}
client, err := ssh.Dial("tcp", addr+":"+strconv.Itoa(port), &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
HostKeyCallback: ssh.HostKeyCallback(func(string, net.Addr, ssh.PublicKey) error { return nil }),
})
if client == nil || err != nil {
return nil, err
}
client.SendRequest(user+"#"+addr, true, nil) // keep alive
return client, nil
}
try DialWithPublickey(host, port, user, "~/.ssh/id_rsa")

Golang SSH Source Port

I have a requirement to use a static source port. We will do an IPTables redirect rule based on this source port. So, the static source port is used as an identifier as multiple connections are pending to the same destination port on the server. Think poor man's TCP mux a la iptables.
I have followed the Golang examples and cobbled some messy code together. I am not a programmer.
The ssh.dial function handles a lot, that becomes apparent once you use net.dial along with ssh.NewClientConn, ssh.NewClient and ssh.NewSession.
I see there is no ProxyCommand like in OpenSSH config options. I was using:
ssh -o ProxyCommand="ncat --source-port %h %p" ...
to achieve the requirement in a Bash script.
Additionally, I apologise for a loaded question but ncat et al. allow me to reuse the source port immediately.
Whereas Golang SSH leaves a TIME-WAIT 0 0 192.168.99.53:31337 192.168.99.7:22 for 60 seconds on Arch Linux.
Obviously, subsequent binds to said source port result in an error.
WRT the below code:
I have omitted the ExampleHostKeyCheck function
I know the sClient.Listen is a poor attempt at getting this work
The remote port forward does NOT appear on the server
I'm assuming a LOT more code is now required to handle the channels etc.?
The shell command does work, the file appears in /tmp/
package main
import (
"bytes"
"log"
"net"
"os"
"bufio"
"strings"
"path/filepath"
"golang.org/x/crypto/ssh"
)
func main() {
hostKey, err := ExampleHostKeyCheck()
conf := &ssh.ClientConfig{
User: "robert",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
server, _ := net.ResolveTCPAddr("tcp", "centos7.ephemeric.local:22")
client, _ := net.ResolveTCPAddr("tcp", ":31337")
cc, err := net.DialTCP("tcp", client, server)
//cc, err := net.DialTCP("tcp", nil, server)
if err != nil {
log.Fatalf("%s", err)
}
defer cc.Close()
conn, chans, reqs, err := ssh.NewClientConn(cc, "", conf)
if err != nil {
log.Fatalf("%s", err)
}
defer conn.Close()
// https://stackoverflow.com/questions/35906991/go-x-crypto-ssh-how-to-establish-ssh-connection-to-private-instance-over-a-ba
sClient := ssh.NewClient(conn, chans, reqs)
if err != nil {
log.Fatalf("%s", err)
}
defer sClient.Close()
session, err := sClient.NewSession()
if err != nil {
log.Fatalf("%s", err)
}
defer session.Close()
sListen, err := sClient.Listen("tcp", "127.0.0.1:31337")
if err != nil {
log.Fatal("unable to register tcp forward: ", err)
}
defer sListen.Close()
var b bytes.Buffer // import "bytes"
session.Stdout = &b // get output
// You can also pass what gets input to the stdin, allowing you to pipe content from client to server session.Stdin = bytes.NewBufferString("MyInput").
err = session.Run("echo slobwashere >>/tmp/slobwashere; ls")
}
Thank you.

SSH through bastion host

I've just started to use Go and I am trying to setup an ssh connection through a bastion host, i successfully authenticate to the bastion host, but fail on the LAN host. I've read a number of posts, the answer to this i've found very helpful. But i'm not sure what would be in that persons config. My code is as follows. I'm trying to do with with PublicKeys only and if its important i'm starting on a mac, authenticate to linux, then fail to make the second connection to another linux host. Plain ssh works fine
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"io/ioutil"
"log"
"os/user"
)
const TCP = "tcp"
const PORT = "22"
func bastionConnect(bastion string, localh string) *ssh.Client {
var usr, _ = user.Current()
var homeDir = usr.HomeDir
fmt.Printf("home is %v\n", homeDir)
key, err := ioutil.ReadFile(homeDir + "/.ssh/id_rsa")
if err != nil {
fmt.Print("i'm dying at reading ssh key")
panic(err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
fmt.Print("i'm dying at parsing private key")
panic(err)
}
fmt.Printf("I'm returning public keys for %v", signer.PublicKey())
config := &ssh.ClientConfig{
User: usr.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
bClient, err := ssh.Dial(TCP, bastion+":22", config)
if err != nil {
log.Fatal(err)
}
fmt.Print("passed bastion host\n")
// Dial a connection to the service host, from the bastion
conn, err := bClient.Dial(TCP, fmt.Sprintf("%s:%s", localh, PORT))
if err != nil {
log.Fatal(err)
}
ncc, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%s:%s", localh, PORT), config)
if err != nil {
fmt.Printf("Error trying to conntect to %s via bastion host\n%v\n", localh, err)
log.Fatal(err)
}
sClient := ssh.NewClient(ncc, chans, reqs)
return sClient
}
func main() {
var bastion = "jumpdev.example.org"
var lanHost = "devserver01"
bastionConnect(bastion, lanHost)
}
The last log line i see is Error trying to connect to devserver01 via bastion host with an error of
2020/02/03 14:40:17 ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]
Pardon all the Printfs needed to see what's up.
In the second connect could the public key config be messing it up? I have also checked out this project, but seems like overkill.
The above code was fine, i was running into an authorized_keys issue on a box that i always connect to but forgot about my local .ssh/config :(
I wanted to expand on this a bit so it was not just whoops, i messed up post. For a full bastion to lanhost agent connection, I have updated a gist here

elogrus no active connection found

I'm using elasticsearch, kibana on docker, and Go.
time="2019-09-17T09:52:02+08:00" level=panic msg="no active connection found: no Elasticsearch node available"
panic: (*logrus.Entry) (0x736fe0,0xc000136150)
import (
"github.com/olivere/elastic/v7"
"github.com/sirupsen/logrus"
"gopkg.in/sohlich/elogrus.v7"
)
func main() {
log := logrus.New()
client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
if err != nil {
log.Panic(err)
}
hook, err := elogrus.NewAsyncElasticHook(client, "localhost", logrus.DebugLevel, "mylog")
if err != nil {
log.Panic(err)
}
log.Hooks.Add(hook)
log.WithFields(logrus.Fields{
"name": "joe",
"age": 42,
}).Error("Hello world!")
}
This is the package I'm using
https://github.com/sohlich/elogrus
You have to authenticate, if you didn't change default password you might use something like this:
Also set off the sniff. More details about it: https://github.com/olivere/elastic/wiki/Sniffing
client, err := elastic.NewClient(
elastic.SetBasicAuth("elastic", "changeme"),
elastic.SetURL("http://localhost:9200"),
elastic.SetSniff(false),
)

How do I execute a command on a remote machine in a golang CLI?

How do I execute a command on a remote machine in a golang CLI? I need to write a golang CLI that can SSH into a remote machine via a key and execute a shell command. Furthermore, I need to be able to do this one hop away. e.g. SSH into a machine (like a cloud bastion) and then SSH into another, internal, machine and execute a shell command.
I haven't (yet) found any examples for this.
You can run commands on a remote machine over SSH using the "golang.org/x/crypto/ssh" package.
Here is an example function demonstrating simple usage of running a single command on a remote machine and returning the output:
//e.g. output, err := remoteRun("root", "MY_IP", "PRIVATE_KEY", "ls")
func remoteRun(user string, addr string, privateKey string, cmd string) (string, error) {
// privateKey could be read from a file, or retrieved from another storage
// source, such as the Secret Service / GNOME Keyring
key, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return "", err
}
// Authentication
config := &ssh.ClientConfig{
User: user,
// https://github.com/golang/go/issues/19767
// as clientConfig is non-permissive by default
// you can set ssh.InsercureIgnoreHostKey to allow any host
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
//alternatively, you could use a password
/*
Auth: []ssh.AuthMethod{
ssh.Password("PASSWORD"),
},
*/
}
// Connect
client, err := ssh.Dial("tcp", net.JoinHostPort(addr, "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 // import "bytes"
session.Stdout = &b // get output
// you can also pass what gets input to the stdin, allowing you to pipe
// content from client to server
// session.Stdin = bytes.NewBufferString("My input")
// Finally, run the command
err = session.Run(cmd)
return b.String(), err
}
Try with os/exec https://golang.org/pkg/os/exec/ to execute a ssh
package main
import (
"bytes"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("ssh", "remote-machine", "bash-command")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
}
To jump over machines use the ProxyCommand directive in a ssh config file.
Host remote_machine_name
ProxyCommand ssh -q bastion nc remote_machine_ip 22
The other solutions here will work, but I'll throw out another option you could try: simplessh. I think it is easier to use. For this question, I would use option 3 below where you can ssh using your key.
Option 1: SSH to a machine with a password, then run a command
import (
"log"
"github.com/sfreiberg/simplessh"
)
func main() error {
var client *simplessh.Client
var err error
if client, err = simplessh.ConnectWithPassword("hostname_to_ssh_to", "username", "password"); err != nil {
return err
}
defer client.Close()
// Now run the commands on the remote machine:
if _, err := client.Exec("cat /tmp/somefile"); err != nil {
log.Println(err)
}
return nil
}
Option 2: SSH to a machine using a set of possible passwords, then run a command
import (
"log"
"github.com/sfreiberg/simplessh"
)
type access struct {
login string
password string
}
var loginAccess []access
func init() {
// Initialize all password to try
loginAccess = append(loginAccess, access{"root", "rootpassword1"})
loginAccess = append(loginAccess, access{"someuser", "newpassword"})
}
func main() error {
var client *simplessh.Client
var err error
// Try to connect with first password, then tried second else fails gracefully
for _, credentials := range loginAccess {
if client, err = simplessh.ConnectWithPassword("hostname_to_ssh_to", credentials.login, credentials.password); err == nil {
break
}
}
if err != nil {
return err
}
defer client.Close()
// Now run the commands on the remote machine:
if _, err := client.Exec("cat /tmp/somefile"); err != nil {
log.Println(err)
}
return nil
}
Option 3: SSH to a machine using your key
import (
"log"
"github.com/sfreiberg/simplessh"
)
func SshAndRunCommand() error {
var client *simplessh.Client
var err error
// Option A: Using a specific private key path:
//if client, err = simplessh.ConnectWithKeyFile("hostname_to_ssh_to", "username", "/home/user/.ssh/id_rsa"); err != nil {
// Option B: Using your default private key at $HOME/.ssh/id_rsa:
//if client, err = simplessh.ConnectWithKeyFile("hostname_to_ssh_to", "username"); err != nil {
// Option C: Use the current user to ssh and the default private key file:
if client, err = simplessh.ConnectWithKeyFile("hostname_to_ssh_to"); err != nil {
return err
}
defer client.Close()
// Now run the commands on the remote machine:
if _, err := client.Exec("cat /tmp/somefile"); err != nil {
log.Println(err)
}
return nil
}
golang SSH executes shell command with timeout option
import (
"bytes"
"context"
"errors"
"fmt"
"golang.org/x/crypto/ssh"
"time"
)
func SshRemoteRunCommandWithTimeout(sshClient *ssh.Client, command string, timeout time.Duration) (string, error) {
if timeout < 1 {
return "", errors.New("timeout must be valid")
}
session, err := sshClient.NewSession()
if err != nil {
return "", err
}
defer session.Close()
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()
resChan := make(chan string, 1)
errChan := make(chan error, 1)
go func() {
// run shell script
if output, err := session.CombinedOutput(command); err != nil {
errChan <- err
} else {
resChan <- string(output)
}
}()
select {
case err := <-errChan:
return "", err
case ms := <-resChan:
return ms, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
Try the package https://github.com/appleboy/easyssh-proxy
package main
import (
"fmt"
"time"
"github.com/appleboy/easyssh-proxy"
)
func main() {
// Create MakeConfig instance with remote username, server address and path to private key.
ssh := &easyssh.MakeConfig{
User: "appleboy",
Server: "example.com",
// Optional key or Password without either we try to contact your agent SOCKET
//Password: "password",
// Paste your source content of private key
// Key: `-----BEGIN RSA PRIVATE KEY-----
// MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
// 7XC9wlRna4b3Ln8ew3q1ZcBjXwD4ppbTlmwAfQIaZTGJUgQbdsO9YA==
// -----END RSA PRIVATE KEY-----
// `,
KeyPath: "/Users/username/.ssh/id_rsa",
Port: "22",
Timeout: 60 * time.Second,
}
// Call Run method with command you want to run on remote server.
stdout, stderr, done, err := ssh.Run("ls -al", 60*time.Second)
// Handle errors
if err != nil {
panic("Can't run remote command: " + err.Error())
} else {
fmt.Println("don is :", done, "stdout is :", stdout, "; stderr is :", stderr)
}
}
See more example.

Resources