以前写过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
自己写程序获取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
按照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的两种认证方式