http.handlefunc 不能直接处理 github webhook 签名验证,因需手动读取并校验 x-hub-signature-256,而 req.body 只能读一次;若提前读取则后续校验失败。
为什么 http.handlefunc 不能直接处理 github webhook 的签名验证
因为 github 发送的 webhook 请求带了 x-hub-signature-256 头,而 http.handlefunc 注册的 handler 只接收原始 *http.request,不自动解析或校验签名。你得手动读取请求体、用密钥重算 hmac-sha256,并和 header 对比——但这里有个关键陷阱:req.body 只能读一次,如果在中间件或日志里提前调用了 io.readall(req.body),后续再读就得到空字节流,导致签名永远不匹配。
实操建议:
- 在 handler 开头立即用
io.ReadAll(req.Body)一次性读完,存为局部变量payload;之后用bytes.NewReader(payload)重建可复用的 body(如果后续还要解析 JSON) - 密钥必须从环境变量读取(如
os.Getenv("WEBHOOK_SECRET")),绝不能硬编码 - 校验失败时返回
http.StatusForbidden,不要泄露“签名错”还是“密钥错”这种细节
如何安全地执行 git pull 和 systemctl restart
Webhook 服务通常以低权限用户(如 www-data 或 deploy)运行,但 git pull 需要 SSH key 或 HTTPS 凭据,systemctl restart 默认需要 root 权限——直接用 exec.Command 调用会因权限不足失败,或因凭据暴露引发安全风险。
实操建议:
- 部署目录的 owner 设为部署用户(如
chown -R deploy:deploy /var/www/myapp),并确保该用户有对应 Git 远程仓库的只读访问权限(推荐用 deploy key +~/.ssh/config绑定 Host) - 用
sudo允许该用户免密码重启特定 service:在/etc/sudoers.d/deploy中加一行deploy ALL=(root) NOPASSWD: /bin/systemctl restart myapp.service - 执行命令前检查当前工作目录是否为预期路径(
os.Chdir("/var/www/myapp")),避免因 cwd 错误导致git操作污染其他项目
net/http.Server 在生产环境必须设超时,否则会卡死
默认的 http.Server 没有设置 ReadTimeout、WriteTimeout 和 IdleTimeout,一旦遇到慢客户端、网络抖动或恶意连接,goroutine 就会堆积,最终耗尽内存或 fd 句柄,整个服务不可用。
实操建议:
- 至少配置
ReadTimeout: 5 * time.Second(防止恶意大 body)和IdleTimeout: 30 * time.Second(清理空闲长连接) - 不要用
log.Fatal(server.ListenAndServe())启动,改用server.ListenAndServe()配合signal.Notify实现优雅关闭 - 监听地址别写
:8080,Webhook 一般走反向代理(Nginx),Go 服务只绑127.0.0.1:8080,避免暴露到公网
Webhook 事件类型判断不能只看 X-GitHub-Event header
GitHub 会发 push、pull_request、release 等多种事件,但有些事件(比如 pull_request 的 opened、synchronize、closed)都需要不同处理逻辑。只靠 header 判断容易漏掉分支过滤或合并状态检查,导致非主分支的 PR 也触发了生产部署。
实操建议:
- 先解析
payload为map[string]interface{}或专用结构体(如github.PushEvent),再根据具体字段判断:例如push事件要看ref == "refs/heads/main",pull_request事件要看action == "closed" && pr.Merged == true - 对非预期事件直接
return,不记录错误日志(避免刷屏),但可以打一条 debug 级日志说明忽略原因 - 本地测试时用
curl -H "X-Hub-Signature-256: sha256=..." -H "X-GitHub-Event: push" -d @test-payload.json http://localhost:8080/webhook手动模拟,比等 GitHub 触发快得多
最常被忽略的是 payload body 的重复读取问题和 sudo 权限粒度控制——前者导致 webhook 看似成功实则没部署,后者可能让攻击者通过伪造请求获得系统级权限。这两个点不解决,脚本上线后基本等于裸奔。











