Golang 之SSH理解

以前写过Golang通过SSH执行交换机操作,但是对于证书认证这一块没有深究。这次通过读gopkg文件,理解更深了一步。

代码案例

package main

import (
"bytes"
"fmt"
"golang.org/x/crypto/ssh"
"io/ioutil"
"log"
)

func main() {
//hnowhost文件对应/root/.ssh/known_hosts。
var knowhost = []byte("192.168.14.137 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItb...gPfaynABbA/tD1V9pV5w=")
//只关注pubkey解析与否
_, _, pubKey, _, _, err := ssh.ParseKnownHosts(knowhost)
if err != nil {
 log.Fatalf("parseKnowHost error", err)
 return
}
//fmt.Println(pubKey)
//读取本机的私钥
key, err := ioutil.ReadFile("./gotest/insert/id_rsa_2048")
if err != nil {
 log.Fatalf("unable to read private key: %v", err)
}
//获取签名
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
 log.Fatalf("unable to parse private key: %v", err)
}
//设置配置文件
config := &ssh.ClientConfig{
 User: "root",
 Auth: []ssh.AuthMethod{
  //证书验证
  ssh.PublicKeys(signer),
  //密码验证
  //ssh.Password("xxxx"),
 },
 //用于加密期间握手验证主机秘钥的回调函数。就比如第一次连接到一台主机,除了验证,还会弹出一堆信息(让你接受对方公钥)。
 // 用于接收特定主机的秘钥
 HostKeyCallback: ssh.FixedHostKey(pubKey),
 //用于接收任何主机的秘钥
 //HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
//准备建立客户端连接
client, err := ssh.Dial("tcp", "192.168.14.137:22", config)
if err != nil {
 log.Fatalf("unable to connect: %v", err)
}
defer client.Close()
//一个正式用于执行远程命令或者shell的连接
session, err := client.NewSession()
if err != nil {
 log.Fatalf("Failed to create session: ", err)
}
defer session.Close()
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/whoami"); err != nil {
 log.Fatal("Failed to run: " + err.Error())

}
fmt.Println(b.String())

}


ParseKnownHosts

为什么要这个函数,因为我们要通过这个函数获取公钥,用于下面的HostKeyCallBack.

这里对应的是linux中的/root/.ssh/known_hosts(只有建立过连接的才有哦)。详细说明请查找man sshd。

know_hosts的作用:ssh会把你每个你访问过计算机的公钥(public key)都记录在~/.ssh/known_hosts。当下次访问相同计算机时,OpenSSH会核对公钥。如果公钥不同,OpenSSH会发出警告, 避免你受到DNS Hijack之类的***。

名称  算法  公钥
192.168.14.137 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPQphM3fQtoe5vq//ROvwfAI6aUAt8VtNzj/GdGCeYU1VJMrWrS/wbwnUYZ3DPVEG7wgPfaynABbA/tD1V9pV5w=


怎么获取这种文件呢:这里提供三种方式:

   1. 通过官方库读取本地的know_host文件。(golang.org/x/crypto/ssh/knownhosts)

hostKeyCallback, err := knownhosts.New("/Users/user/.ssh/known_hosts")

参考链接: key Verification

  1. 自己写程序获取know_host文件

    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
    }

    参考链接:ssh handshake

  2. 按照know_hosts格式,手动写一行数据,参照第一行代码。

    使用ssh-keyscan获取know_host信息,建议-t 写成ecdsa。rsa好像没有生效。

    ssh-keyscan -v  -t ecdsa 192.168.14.137
    192.168.14.137 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItb...gPfaynABbA/tD1V9pV5w=
    ---
    var knowhost = []byte("192.168.14.137 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPQphM3fQtoe5vq//ROvwfAI6aUAt8VtNzj/GdGCeYU1VJMrWrS/wbwnUYZ3DPVEG7wgPfaynABbA/tD1V9pV5w=")
    //只关注pubkey解析与否
    _, _, pubKey, _, _, err := ssh.ParseKnownHosts(knowhost)

    参考链接: ssh-keyscan命令详解


HostKeyCallback

type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error

HostKeyCallback是用于验证服务器密钥的函数类型。

FixedHostKey(pubKey),用于接收特定主机的秘钥,用于生产。 InsecureIgnoreHostKey() 用于接收任何主机的秘钥。

当然也可以自定义实现类型: 比如上例。

func New(files ...string) (ssh.HostKeyCallback, error)

参考链接: knownhosts


ParsePrivateKey

signer, err := ssh.ParsePrivateKey(key)
config := &ssh.ClientConfig{
 User: "root",
 Auth: []ssh.AuthMethod{
  //证书验证
  ssh.PublicKeys(signer),
  //密码验证
  //ssh.Password("xxxx"),
 },

ParsePrivateKey从PEM编码的私钥返回签名者。 它支持与ParseRawPrivateKey相同的键。 如果私钥已加密,它将返回PassphraseMissingError。

主要是免密登录中的秘钥认证。举例

1、登录A机器 2、ssh-keygen -t [rsa|dsa],将会生成密钥文件和私钥文件 id_rsa,id_rsa.pub或id_dsa,id_dsa.pub 3、将 .pub 文件复制到B机器的 .ssh 目录, 并 cat id_dsa.pub >> ~/.ssh/authorized_keys 4、大功告成,从A机器登录B机器的目标账户,不再需要密码了;(直接运行 #ssh 192.168.20.60 )


简单来说: 当你将你的公钥文件拷贝到对方主机的/root/.ssh/authorized_keys中去,那么你下次登录时候,选择证书认证的时候就不要输入密码了。

参考链接: ssh的两种认证方式




参考链接:

https://pkg.go.dev/golang.org/x/crypto/ssh#HostKeyCallback

ParseKnownHosts

knownhosts

HostKeyCallback

key Verification

ssh handshake

ssh-keyscan命令详解

knownhosts

ssh的两种认证方式

Validating host keys in Go's ssh package


猜你喜欢

转载自blog.51cto.com/9406836/2673363