go ssh实现

golang 实现ssh秘钥和密码登录, 通过golang.org/x/crypto/ssh库实现.

post thumb
Algorithm
作者 Louis 发表于 2019年9月1日

[TOC]

ssh

SSH全称Secure Shell是一种工作在应用层和传输层上的安全协议,能在非安全通道上建立安全通道。提供身份认证、密钥更新、数据校验、通道复用等功能,同时具有良好的可扩展性,由芬兰赫尔辛基大学研究员Tatu Ylönen,于1995年提出,其目的是用于替代非安全的Telnet、rsh、rexec等远程Shell协议。之后SSH发展了两个大版本,SSH-1和SSH-2, 开源实现OpenSSH对2者都支持。

SSH的主要特性:

  • 加密: 避免数据内容泄漏
  • 通信的完整性: 避免数据被篡改,以及发送或接受地址伪装(检查数据是否被篡改,数据是否来自发送者而非攻击者) SSH-2通过MD5和SHA-1实现该功能,SSH-1使用CRC-32
  • 认证: 识别数据发送者和接收者身份 客户端验证SSH服务端的身份:防止攻击者仿冒SSH服务端身份,避免中介人攻击和重定向请求的攻击;OpenSSH通过在know-hosts中存储主机名和host key对服务端身份进行认证 服务端验证请求者身份:提供安全性较弱的用户密码方式,和安全性更强的per-user public-key signatures;此外SSH还支持与第三方安全服务系统的集成,如Kerberos等
  • 授权: 用户访问控制
  • 安全隧道: 转发或者为基于TCP/IP的回话提供加密隧道, 比如通过SSH为Telnet、FTP等提供通信安全保障,支持三种类型的Forwarding操作:Port Forwarding;X Forwarding;Agent Forwarding

通过使用SSH,你可以把所有传输的数据进行加密,这样”中间人”这种攻击方式就不可能实现了,而且也能够防止DNS欺骗和IP欺骗。使用SSH,还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度。SSH有很多功能,它既可以代替Telnet,又可以为FTP、PoP、甚至为PPP提供一个安全的”通道”。

Go中ssh客户端实现

实现远程执行命令

/*
@Time : 2019/8/30 11:22
@Author : louis
@File : ssh
@Software: GoLand
*/

package main

import (
	"errors"
	"fmt"
	flag "github.com/spf13/pflag"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
	"io/ioutil"
	"log"
	"net"
	"os"
	"time"
)

type Conn struct {
	user string
	path string
	auth []ssh.AuthMethod
	addr string
}

var (
	client   *ssh.Client
	session  *ssh.Session
	password string
	host     string
	path     string
	port     int
	user     string
	help     bool
)

func usage() {
	_, _ = fmt.Fprintf(os.Stderr, `sshgo version:1.0.0
Usage sshgo [-h host] [-P port] [-u user] [--pkPath path] [-p password]

example: 1) sshgo -u root -P 9527 -h 1.1.1.1 --pkPath /path/to/id_rsa -p 123456
         2) sshgo --user root --port 9527 --host 1.1.1.1 --pkPath /path/to/id_rsa --password 123456 

if use key to login, the password is the key password; if use user & pasword; the password is for user.

Options:
`)
	flag.PrintDefaults()
}

func init() {
	flag.StringVarP(&password, "password", "p", "", "" +
		"if use key to login, the password is the key password; " +
		"if use user & password to login; the password is for user.")
	flag.StringVarP(&path, "pkPath", "", "", "private key path")
	flag.StringVarP(&host, "host", "h", "fenghong.tech", "remote host addr ip")
	flag.IntVarP(&port, "port", "P", 9527, "remote host port")
	flag.StringVarP(&user, "user", "u", "root", "remote host user")
	flag.BoolVarP(&help, "help", "", false, "this help")
	flag.Usage = usage
}

func (c *Conn) SetConf() (err error) {
	c.addr = fmt.Sprintf("%s:%d", host, port)
	c.user = user
	c.path = path
	c.auth = make([]ssh.AuthMethod, 0)
	var method ssh.AuthMethod
	// use privatakey to login,defualt is ""
	if path != "" {
		c.auth = make([]ssh.AuthMethod, 0)
		method, err = PublicFile(path, password)
		if err != nil {
			return err
		}
        // use password & user to login
	} else {
		method = ssh.Password(password)
	}
	c.auth = append(c.auth, method)
	return nil
}

func (c *Conn) SetSession() (session *ssh.Session, err error) {
	client, err = ssh.Dial("tcp", c.addr, &ssh.ClientConfig{
		User: c.user,
		Auth: c.auth,
		//需要验证服务端,不做验证返回nil就可以,点击HostKeyCallback看源码就知道了
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: time.Second * 2,
	})
	if err != nil {
		fmt.Println(err)
		return nil, err
	}

	// create session
	if session, err = client.NewSession(); err != nil {
		return nil, err
	}

	return session, nil
}

func isFile(path string) bool {
	_, err := os.Stat(path)
	return err == nil || os.IsExist(err)
}

//采用公钥验证,这里封装了一下,使用秘钥+密码验证
func PublicFile(privateKeyPath, password string) (method ssh.AuthMethod, err error) {
	if !isFile(privateKeyPath) {
		return nil, errors.New("file not exist")
	}
	bufKey, err := ioutil.ReadFile(privateKeyPath)
	if err != nil {
		return nil, err
	}
	var key ssh.Signer
	if password == "" {
		key, err = ssh.ParsePrivateKey(bufKey)
		if err != nil {
			return nil, err
		}
	} else {
		bufPwd := []byte(password)
		key, err = ssh.ParsePrivateKeyWithPassphrase(bufKey, bufPwd)
		if err != nil {
			return nil, err
		}
	}
	return ssh.PublicKeys(key), nil
}




func main() {
	flag.Parse()
	if help {
		flag.Usage()
		return
	}
	c := Conn{}
	err := c.SetConf()
	if err != nil {
		log.Fatalln(err)
	}
	session, err = c.SetSession()
	if err != nil {
		log.Fatalln(err)
	}
	defer session.Close()
	//当ssh连接建立过后, 我们就可以通过这个连接建立一个回话, 在回话上和远程主机通信。
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	modes := ssh.TerminalModes{
		ssh.ECHO:1,
		ssh.ECHOCTL:0,
		ssh.TTY_OP_ISPEED:14400,
		ssh.TTY_OP_OSPEED:14400,
	}
	termFd := int(os.Stdin.Fd())
	w,h,_ := terminal.GetSize(termFd)
	termState, _ := terminal.MakeRaw(termFd)
	// TODO 主动从服务器exit,会导致空指针.
	defer terminal.Restore(termFd,termState)

	err = session.RequestPty("xterm-256color",h,w,modes)
	if err != nil {
		log.Fatalln(err)
	}
	err = session.Shell()
	if err != nil {
		log.Fatalln(err)
	}
	err = session.Wait()
	if err != nil {
		log.Fatalln(err)
	}
}

  • 实现交互命令

远程执行命令和交互命令认证过程都一样,唯一区别的是会话

var cmd string 
// init 函数加上命令行参数
flag.StringVarP(&cmd, "cmd", "c", "pwd", "execute cmd in server")

func main() {
	flag.Parse()
	if help {
		flag.Usage()
		return
	}
	c := Conn{}
	err := c.SetConf()
	if err != nil {
		log.Fatalln(err)
	}
	session, err = c.SetSession()
	if err != nil {
		log.Fatalln(err)
	}
	defer session.Close()

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	_ = session.Run(cmd)
}

验证

### 交互命令式验证
$ go build sshTunel.go
$ ./sshTunel.exe
Last login: Mon Sep  2 00:43:39 2019 from 183.192.10.3

                        Welcome to Alibaba Cloud Elastic Compute Service !

                Welcome to the testing environment of Louis.
                Feel free to use this system for testing your Linux
                skills. In case of any issues reach out to admin at
                louisehong4168@gmail.com. Thank you.


[oh-my-zsh] Random theme '/root/.oh-my-zsh/themes/gnzh.zsh-theme' loaded...
╭─root@master-louis ~ system: ruby 2.0.0p648
╰─➤


## 执行命令式
> go run ssh.go -a "d:/text/id_rsa" -c pwd
/root

> go run ssh.go -u feng -w password -c pwd
/home/feng

初步实现ssh客户端功能

上一篇
kubeadm 安装 kubernetes1.15.3