生产环境必须禁用 ssh.InsecureIgnoreHostKey(),否则面临中间人攻击;HostKeyCallback 应使用 ssh.KnownHosts() 或 ssh.FixedHostKey();每次命令需新建 ssh.Session 并 defer Close();超时须用 context.WithTimeout();传文件应使用 sftp.Client 而非 session.Run("scp")。

生产环境必须禁用 ssh.InsecureIgnoreHostKey(),否则连接看似成功,实则完全暴露于中间人攻击——这是最常被忽略、也最危险的配置错误。
HostKeyCallback 不能填 ssh.InsecureIgnoreHostKey()
Go 的 ssh.Dial() 强制要求提供 HostKeyCallback,但它不是“随便填个能过编译就行”的参数。填 ssh.InsecureIgnoreHostKey() 在本地调试可以,但只要部署到 CI/CD、容器或任何非本机环境,就等于主动放弃主机身份验证。
- 推荐用
ssh.KnownHosts(knownHostsFile),路径需显式展开:比如os.UserHomeDir() + "/.ssh/known_hosts",不能直接写"~/.ssh/known_hosts" - 若需预置密钥(如测试环境固定 IP),可用
ssh.FixedHostKey(pubKey),但必须先用ssh.ParsePublicKey()解析ssh-keyscan -t rsa host拿到的公钥内容 - 错误现象:
ssh: handshake failed: ssh: unable to authenticate常被误判为密码或密钥问题,实际是HostKeyCallback拒绝了服务端公钥 —— 此时应检查日志中打印的远端公钥指纹是否与预期一致
每次执行命令都得新建 *ssh.Session
*ssh.Session 是一次性对象,不是连接池资源,也不线程安全。复用会导致 write tcp: broken pipe 或静默丢命令,尤其在并发调用时非常隐蔽。
- 必须在每次
client.NewSession()后配defer session.Close() - 用
session.Run("ls")或session.Output("whoami")即可,它们内部已自动调用Wait(),无需再手动session.Wait() - 需要交互(如输入密码、响应提示)才用
session.Start()+session.StdinPipe(),且必须自己管理生命周期
超时控制必须用 context.WithTimeout()
ClientConfig.Timeout 只影响握手阶段,对命令执行无约束。不加 context 超时,session.Run() 卡死就真卡死了,没有 fallback。
- 正确写法:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second); defer cancel(),然后传给session.Run()(注意:部分老版本需包装session的底层Conn,但 v0.20+ 支持直接传 ctx) - 别写
client.Do()—— 这个方法根本不存在,golang.org/x/crypto/ssh没这封装,编译直接报错
传文件别碰 session.Run("scp")
用 shell 调 scp 是典型“本地能跑,上线必崩”:远端没 scp、shell 被设为 /bin/false、路径含空格或中文,全都会失败。
- 传文件请走
sftp.Client,它基于 SSH 子系统,协议级支持,不依赖远端 shell 环境 -
session.Run("ls")和sftp.Client是两套独立路径,别混用;前者只适合轻量命令,后者才是文件操作正解
真正难的不是连上,而是让每次连接都可审计、可中断、可验证——HostKeyCallback 的选择、Session 的生命周期、context 超时的嵌套位置,这三个点一旦写错,问题往往延迟暴露,排查成本远高于初始编码成本。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











