WinRM for Go — 用 Go 语言远程管理 Windows 机器
WinRM for Go 是一个纯 Go 语言编写的库,让你能通过 WinRM/WinRS 协议在 Windows 机器上执行远程命令。它支持本地账户的基本认证、NTLM 认证以及 Kerberos 域用户认证,甚至可以通过 SSH 隧道穿透网络。本文从环境准备到代码实战,带你一步步上手这个工具。
01 为什么需要 WinRM for Go
日常运维中经常要远程操作 Windows 服务器,尤其是云上的 EC2 实例。传统做法是开 RDP 进去手动执行命令,但在自动化场景下显然不现实。PowerShell Remoting 是个方案,可如果你是 Go 开发者,想直接把远程执行命令集成到自己的工具或服务里,就得找个好用的库。
WinRM for Go 就是干这个的。 它用 Go 实现了 WinRM(Windows Remote Management)协议,让你在代码里就能对 Windows 机器执行 shell 命令、获取输出,就像 SSH 连 Linux 一样自然。
02 核心功能
| 功能 | 说明 |
|---|---|
| 基本认证 | 支持本地账户 |
| NTLM 认证 | 比如连 Azure 虚拟机 |
| Kerberos 认证 | 域用户环境 |
| HTTPS + 客户端证书 | 更安全的认证方式 |
| 自定义 dialer | 例如通过 SSH 隧道中转 |
| 纯 Go 实现 | 不依赖 CGO 或外部库 |
03 环境准备:Windows 端配置
3.1 基本认证(本地账户)
要在远程 Windows 上启用 WinRM,需要以管理员身份运行 PowerShell,执行以下命令:
winrm quickconfig
# 输入 y 确认
winrm set winrm/config/service/Auth '@{Basic="true"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
几个注意点:
- Windows 防火墙必须运行
- 别禁用 Negotiate 认证,因为 WinRM 自身依赖它
- 如果你的 Windows 2008R2 有内存限制的 bug,需要打 KB2842230 补丁
3.2 Kerberos 认证(域用户)
如果你需要用域账号连接,配置稍有不同:
winrm quickconfig
# 输入 y 确认
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
注意:不需要设置 Basic=true,因为 Kerberos 本身加密。
04 安装与构建
确保你的 Go 版本不低于 1.5(新版建议 1.7+)。克隆项目后直接编译:
git clone https://github.com/masterzen/winrm
cd winrm
make
如果你只是想作为库引用,直接 go get 即可:
go get github.com/masterzen/winrm
05 代码实战:四种典型用法
5.1 最快上手:HTTP 明文
package main
import (
"github.com/masterzen/winrm"
"os"
"context"
)
func main() {
endpoint := winrm.NewEndpoint(host, 5986, false, false, nil, nil, nil, 0)
client, err := winrm.NewClient(endpoint, "Administrator", "secret")
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client.RunWithContext(ctx, "ipconfig /all", os.Stdout, os.Stderr)
}
endpoint 参数说明:
winrm.NewEndpoint(
host, // 主机地址
5986, // 端口(HTTP 5985,HTTPS 5986)
false, // 是否 HTTPS
false, // 是否跳过证书验证
nil, // CA 证书
nil, // 客户端证书
nil, // 客户端密钥
0, // 超时(0 表示不限制)
)
5.2 支持输入:交互式命令
package main
import (
"github.com/masterzen/winrm"
"os"
"context"
)
func main() {
endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)
client, err := winrm.NewClient(endpoint, "Administrator", "secret")
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
panic(err)
}
}
差别:第三个参数传入了 stdin,可以让命令交互。
5.3 NTLM 认证(连接 Azure VM)
package main
import (
"github.com/masterzen/winrm"
"os"
)
func main() {
endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)
params := winrm.DefaultParameters
params.TransportDecorator = func() winrm.Transporter {
return &winrm.ClientNTLM{}
}
client, err := winrm.NewClientWithParameters(endpoint, "test", "test", params)
if err != nil {
panic(err)
}
_, err = client.RunWithInput("ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
panic(err)
}
}
核心:自定义 TransportDecorator,换成 NTLM 认证器。
5.4 通过 SSH 隧道连接内网机器
package main
import (
"github.com/masterzen/winrm"
"golang.org/x/crypto/ssh"
"os"
"context"
)
func main() {
// 1. 先建立 SSH 隧道
sshClient, err := ssh.Dial("tcp", "localhost:22", &ssh.ClientConfig{
User: "ubuntu",
Auth: []ssh.AuthMethod{ssh.Password("ubuntu")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
panic(err)
}
// 2. 通过 SSH 隧道连接 WinRM
endpoint := winrm.NewEndpoint("other-host", 5985, false, false, nil, nil, nil, 0)
params := winrm.DefaultParameters
params.Dial = sshClient.Dial // 关键:使用 SSH 隧道的 Dial
client, err := winrm.NewClientWithParameters(endpoint, "test", "test", params)
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client.RunWithContext(ctx, "ipconfig", os.Stdout, os.Stderr)
}
场景:Windows 机器在内网,无法直接访问,需要通过跳板机(SSH)中转。
06 常见问题
Q1: WinRM 端口是多少?
- HTTP: 5985
- HTTPS: 5986
Q2: 如何启用 HTTPS?
# 创建自签名证书
$cert = New-SelfSignedCertificate -DnsName "your-server" -CertStoreLocation "Cert:\LocalMachine\My"
# 配置 WinRM 使用 HTTPS
winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname="your-server";CertificateThumbprint="$($cert.Thumbprint)"}'
Q3: 如何调试连接问题?
# 查看 WinRM 配置
winrm get winrm/config
# 测试连接
winrm identify -remote:localhost
07 对比其他方案
| 方案 | 语言 | 优点 | 缺点 |
|---|---|---|---|
| WinRM for Go | Go | 纯 Go,易集成 | 需要配置 WinRM |
| PowerShell Remoting | PowerShell | 原生支持 | 仅限 PowerShell 脚本 |
| WMI | 任意 | 广泛支持 | 功能受限 |
| RDP | GUI | 可视化 | 无法自动化 |
08 相关链接
- GitHub: https://github.com/masterzen/winrm
- WinRM 官方文档: https://docs.microsoft.com/en-us/windows/win32/winrm/portal
- Go SSH 库: https://pkg.go.dev/golang.org/x/crypto/ssh
总结
WinRM for Go 让 Go 开发者能够像 SSH 连 Linux 一样远程操作 Windows 服务器:
- ✅ 纯 Go 实现:无 CGO 依赖,跨平台编译
- ✅ 多种认证:Basic、NTLM、Kerberos、HTTPS 客户端证书
- ✅ SSH 隧道:穿透内网,连接隔离环境的 Windows 机器
- ✅ 交互式命令:支持 stdin 输入,适合交互式脚本
如果你需要在 Go 项目中远程管理 Windows 服务器,WinRM for Go 是不二之选。
原文来自微信公众号。