直接用net/http.listenandserve无法通过github校验,核心是x-hub-signature-256校验失败:必须截取sha256=前缀后hex解码,用hmac.equal比对hmac-sha256计算结果,且需一次性读取原始body、密钥从环境变量安全获取。

直接用 net/http.ListenAndServe 启动 Webhook 服务无法通过 GitHub 校验,核心问题不是“收不到请求”,而是签名比对失败后 GitHub 静默丢弃事件——你根本不知道部署没触发。
为什么 X-Hub-Signature-256 校验总失败
GitHub 的签名头不是拿来“看看就行”的,它要求严格匹配:值格式为 sha256=xxx,必须截掉前缀、hex 解码后再和 HMAC-SHA256 计算结果用 bytes.Equal 比对。常见错误包括:
- 用
strings.Contains或==直接比字符串,既不安全(时序攻击风险),又容易因空格或大小写误判 - 在
json.NewDecoder(r.Body).Decode()前没调用io.ReadAll(r.Body),导致 body 已 EOF,后续读取返回空字节流 - 密钥从硬编码字符串或命令行参数读取,上线后暴露在
ps aux进程列表里
如何安全解析 push 事件 payload
GitHub 的 push 事件结构松散,字段类型不保证稳定,比如 head_commit.id 可能是字符串也可能是数字。用 map[string]interface{} 解析会 panic,推荐定义结构体并配合 json.RawMessage:
- 对不确定字段(如
HeadCommit)声明为json.RawMessage,延后解析 - 必用字段(如
Repository.FullName、After)单独提取并校验非空,避免空指针 -
Before和After是 commit SHA,不是 ref 名;真正分支名在Ref字段,值形如refs/heads/main - 不要靠
Commits数组长度判断是否新建分支——只有Created字段为true才表示新建分支
怎么避免并发 push 导致部署错乱
GitHub 可能在几秒内发多个 push 事件,Go 默认 handler 并发执行,若每个都直接 git pull + systemctl restart,极易因竞态导致状态不一致或进程卡死。轻量级解法是加一层队列:
- HTTP handler 只做三件事:读取原始
payload、校验签名、写入chan []byte - 启动独立 goroutine 消费 channel,串行执行解析 → 权限检查 →
exec.Command调用部署二进制 - 部署命令必须设
cmd.Dir(绝对路径)、cmd.Env(保留PATH)、cmd.WaitDelay(超时控制),禁止拼接Ref字段进 shell
本地调试 Webhook 为什么总是 400
本地没公网地址,GitHub 发不出请求是表象;更常被忽略的是模拟请求时漏了关键头:
- 手动
curl必须带-H "Content-Type: application/json"和-H "X-Hub-Signature-256: sha256=...",否则 Go 服务跳过校验逻辑直接报错 - 推荐用
smee.io转发真实事件到localhost:8080,比 ngrok 更轻、无域名限制 - handler 开头加一行
log.Printf("raw body: %s", string(payload)),确认收到的是合法 JSON,而不是空字符串或 Nginx 返回的 HTML 错误页
最易被忽略的点:部署命令运行用户(如 deploy)必须有 SSH key 权限拉私有仓库,且 systemctl restart 需要 sudo 权限配置——这两处权限陷阱不会报错,只会让 exec.Command 静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











