Go goph
Contents
Go Goph 模块
github地址:https://github.com/melbahja/goph
go开发封装的 ssh client 模块
Features
- Easy to use and simple API.
- Supports known hosts by default.
- Supports connections with passwords.
- Supports connections with private keys.
- Supports connections with protected private keys with passphrase.
- Supports upload files from local to remote.
- Supports download files from remote to local.
- Supports connections with ssh agent (Unix systems only).
- Supports adding new hosts to known_hosts file.
- Supports file system operations like:
Open, Create, Chmod...
- Supports context.Context for command cancellation.
1、安装
go get github.com/melbahja/goph
2、使用示例
1)使用ssh 执行命令
package main
import (
"log"
"fmt"
"github.com/melbahja/goph"
)
func main() {
// Start new ssh connection with private key.
auth, err := goph.Key("/home/serialt/.ssh/id_rsa", "")
if err != nil {
log.Fatal(err)
}
// goph.New 默认使用22端口,如果非22端口,则参考goph.New的实现
client, err := goph.New("root", "192.1.1.3", auth)
if err != nil {
log.Fatal(err)
}
// Defer closing the network connection.
defer client.Close()
// Execute your command.
out, err := client.Run("ls /tmp/")
if err != nil {
log.Fatal(err)
}
// Get your output as []byte.
fmt.Println(string(out))
}
2)密钥带密码
auth, err := goph.Key("/home/serialt/.ssh/id_rsa", "you_passphrase_here")
if err != nil {
// handle error
}
client, err := goph.New("root", "192.1.1.3", auth)
3)使用密码连接
auth, err := goph.UseAgent()
if err != nil {
// handle error
}
client, err := goph.New("root", "192.1.1.3", auth)
4)上传和下载文件
// upload local file to remote
err := client.Upload("/path/to/local/file", "/path/to/remote/file")
// download remote file to local
err := client.Download("/path/to/remote/file", "/path/to/local/file")
5)执行shell命令
// execute bash commands
out, err := client.Run("bash -c 'printenv'")
// execute bash command whith timeout
context, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
// will send SIGINT and return error after 1 second
out, err := client.RunContext(ctx, "sleep 5")
// execute bash command whith env variables
out, err := client.Run(`env MYVAR="MY VALUE" bash -c 'echo $MYVAR;'`)
6)使用goph cmd
Goph.Cmd
struct is like the Go standard os/exec.Cmd
.
// Get new `Goph.Cmd`
cmd, err := client.Command("ls", "-alh", "/tmp")
// or with context:
// cmd, err := client.CommandContext(ctx, "ls", "-alh", "/tmp")
if err != nil {
// handle the error!
}
// You can set env vars, but the server must be configured to `AcceptEnv line`.
cmd.Env = []string{"MY_VAR=MYVALUE"}
// Run you command.
err = cmd.Run()
ust like os/exec.Cmd
you can run CombinedOutput, Output, Start, Wait
, and ssh.Session
methods like Signal
…
7)使用sftp操作文件系统
sftp, err := client.NewSftp()
if err != nil {
// handle the error!
}
file, err := sftp.Create("/tmp/remote_file")
file.Write([]byte(`Hello world`))
file.Close()
For more file operations see SFTP Docs.
3、官方示例
package main
import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"log"
"net"
"os"
"strings"
"time"
"github.com/melbahja/goph"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
//
// Run command and auth via password:
// > go run main.go --ip 192.168.122.102 --pass --cmd ls
//
// Run command and auth via private key:
// > go run main.go --ip 192.168.122.102 --cmd ls
// Or:
// > go run main.go --ip 192.168.122.102 --key /path/to/private_key --cmd ls
//
// Run command and auth with private key and passphrase:
// > go run main.go --ip 192.168.122.102 --passphrase --cmd ls
//
// Run a command and interrupt it after 1 second:
// > go run main.go --ip 192.168.122.102 --cmd "sleep 10" --timeout=1s
//
// You can test with the interactive mode without passing --cmd flag.
//
var (
err error
auth goph.Auth
client *goph.Client
addr string
user string
port uint
key string
cmd string
pass bool
passphrase bool
timeout time.Duration
agent bool
sftpc *sftp.Client
)
func init() {
flag.StringVar(&addr, "ip", "127.0.0.1", "machine ip address.")
flag.StringVar(&user, "user", "root", "ssh user.")
flag.UintVar(&port, "port", 22, "ssh port number.")
flag.StringVar(&key, "key", strings.Join([]string{os.Getenv("HOME"), ".ssh", "id_rsa"}, "/"), "private key path.")
flag.StringVar(&cmd, "cmd", "", "command to run.")
flag.BoolVar(&pass, "pass", false, "ask for ssh password instead of private key.")
flag.BoolVar(&agent, "agent", false, "use ssh agent for authentication (unix systems only).")
flag.BoolVar(&passphrase, "passphrase", false, "ask for private key passphrase.")
flag.DurationVar(&timeout, "timeout", 0, "interrupt a command with SIGINT after a given timeout (0 means no timeout)")
}
func VerifyHost(host string, remote net.Addr, key ssh.PublicKey) error {
//
// If you want to connect to new hosts.
// here your should check new connections public keys
// if the key not trusted you shuld return an error
//
// hostFound: is host in known hosts file.
// err: error if key not in known hosts file OR host in known hosts file but key changed!
hostFound, err := goph.CheckKnownHost(host, remote, key, "")
// Host in known hosts but key mismatch!
// Maybe because of MAN IN THE MIDDLE ATTACK!
if hostFound && err != nil {
return err
}
// handshake because public key already exists.
if hostFound && err == nil {
return nil
}
// Ask user to check if he trust the host public key.
if askIsHostTrusted(host, key) == false {
// Make sure to return error on non trusted keys.
return errors.New("you typed no, aborted!")
}
// Add the new host to known hosts file.
return goph.AddKnownHost(host, remote, key, "")
}
func main() {
flag.Parse()
var err error
if agent || goph.HasAgent() {
auth, err = goph.UseAgent()
} else if pass {
auth = goph.Password(askPass("Enter SSH Password: "))
} else {
auth, err = goph.Key(key, getPassphrase(passphrase))
}
if err != nil {
panic(err)
}
client, err = goph.NewConn(&goph.Config{
User: user,
Addr: addr,
Port: port,
Auth: auth,
Callback: VerifyHost,
})
if err != nil {
panic(err)
}
// Close client net connection
defer client.Close()
// If the cmd flag exists
if cmd != "" {
ctx := context.Background()
// create a context with timeout, if supplied in the argumetns
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}
out, err := client.RunContext(ctx, cmd)
fmt.Println(string(out), err)
return
}
// else open interactive mode.
playWithSSHJustForTestingThisProgram(client)
}
func askPass(msg string) string {
fmt.Print(msg)
pass, err := terminal.ReadPassword(0)
if err != nil {
panic(err)
}
fmt.Println("")
return strings.TrimSpace(string(pass))
}
func getPassphrase(ask bool) string {
if ask {
return askPass("Enter Private Key Passphrase: ")
}
return ""
}
func askIsHostTrusted(host string, key ssh.PublicKey) bool {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Unknown Host: %s \nFingerprint: %s \n", host, ssh.FingerprintSHA256(key))
fmt.Print("Would you like to add it? type yes or no: ")
a, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
return strings.ToLower(strings.TrimSpace(a)) == "yes"
}
func getSftp(client *goph.Client) *sftp.Client {
var err error
if sftpc == nil {
sftpc, err = client.NewSftp()
if err != nil {
panic(err)
}
}
return sftpc
}
func playWithSSHJustForTestingThisProgram(client *goph.Client) {
fmt.Println("Welcome To Goph :D")
fmt.Printf("Connected to %s\n", client.Config.Addr)
fmt.Println("Type your shell command and enter.")
fmt.Println("To download file from remote type: download remote/path local/path")
fmt.Println("To upload file to remote type: upload local/path remote/path")
fmt.Println("To create a remote dir type: mkdirall /path/to/remote/newdir")
fmt.Println("To exit type: exit")
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("> ")
var (
out []byte
err error
cmd string
parts []string
)
loop:
for scanner.Scan() {
err = nil
cmd = scanner.Text()
parts = strings.Split(cmd, " ")
if len(parts) < 1 {
continue
}
switch parts[0] {
case "exit":
fmt.Println("goph bye!")
break loop
case "download":
if len(parts) != 3 {
fmt.Println("please type valid download command!")
continue loop
}
err = client.Download(parts[1], parts[2])
fmt.Println("download err: ", err)
break
case "upload":
if len(parts) != 3 {
fmt.Println("please type valid upload command!")
continue loop
}
err = client.Upload(parts[1], parts[2])
fmt.Println("upload err: ", err)
break
case "mkdirall":
if len(parts) != 2 {
fmt.Println("please type valid mkdirall command!")
continue loop
}
ftp := getSftp(client)
err = ftp.MkdirAll(parts[1])
fmt.Printf("mkdirall err(%v) you can check via: stat %s\n", err, parts[1])
default:
command, err := client.Command(parts[0], parts[1:]...)
if err != nil {
panic(err)
}
out, err = command.CombinedOutput()
fmt.Println(string(out), err)
}
fmt.Print("> ")
}
}