JavaLYG

GitHub webhook 部分投递超时?新增 CIDR 后用 Meta hooks 排查静态白名单

GitHub webhook 接收端用了静态 IP 白名单,近期出现部分投递超时、应用却没有日志?2026 年 10 月 29 日的官方公告新增了三个投递网段。本文按 Meta API 的 hooks 字段核对名单,用离线实验检查候选规则,再把网络、签名和业务验收拆开,避免“放行了却仍漏单”。

一、这次新增什么,谁需要处理

公告列出的 CIDR 是 9.234.98.160/28、9.234.98.176/28、9.234.106.48/28,适用于 github.com 所有计划,包括 GitHub Enterprise Cloud。维护静态入站名单的接收端,应把它们与已有 webhook 网段一起核对,不能只留下这三个新网段。

如果已经用 Meta API 持续同步白名单,官方说明无需为本次新增另做变更。不过,“有同步脚本”不等于“规则已经生效”,仍要看最近一次成功更新时间和实际加载结果。这是 github.com 的公告,不要直接套到自建 GitHub Enterprise Server。

二、先定位请求停在哪里,别急着放宽规则

部分投递失败、部分正常,确实符合名单遗漏的表现,但它不是唯一解释。先对齐 GitHub 投递记录、边界拒绝日志、反向代理访问日志和应用接收记录。应用日志为空,只能说明应用没有记到,不能直接认定是签名错误。

按时间与投递标识关联一次失败,确认请求是否到达边界、在哪层被拒绝,再核对真实来源是否属于当前 hooks。边界拒绝通常不会产生应用日志;如果代理已经返回 HTTP 状态,就继续查路径、限流、上游和处理耗时。不要用临时放开所有来源来替代定位。

GitHub webhook独立工程检查:网络、签名、接收建议与业务结果
图:四项独立工程检查,并非 GitHub 官方四层协议或处理流水线。“持久化后响应”是可靠接收建议,须兼顾十秒响应预算;各项成功互不替代。图片由 DM-Fox 辅助生成并经技术与可读性复核。

三、只读获取 Meta,先生成候选再谈上线

读取入口是 GET https://api.github.com/meta,只取 JSON 中的 hooks,不要混入 web、api 等其他服务字段,也不要复制文档里的示例响应作为长期名单。Meta 返回 IPv4 与 IPv6,但这不等于每项 GitHub 功能都支持 IPv6,目标网络能力仍需单独核查。

实际操作可分四步:用启用证书验证、设有超时的 HTTPS 客户端只读拉取;确认成功状态并解析 JSON;完整校验 hooks 后生成候选,与旧名单比较新增和删除;人工审核后交给既有变更流程。本文不提供自动改生产防火墙的脚本,也不把一次 API 读取当作部署完成。

拉取超时、非成功响应、JSON 损坏、字段缺失或空列表,都应保留旧规则并告警,不能把异常解释成“清空限制”。同时记录最后成功时间,持续失败要升级处理:保留旧值能避免误开放,却仍可能漏掉新来源。删除项或异常扩大范围应单独复核,避免错误候选直接覆盖现网。

四、先在离线环境验证候选门禁

下面是完整 Python 3.9+ 标准库程序。本地实际使用 Python 3.14.3 运行。示例只用文档网段 192.0.2.0/24 和 2001:db8::/32,它们不是 GitHub 真实地址,严禁照抄进生产名单。

校验采用偏严格的本地策略:每项必须是规范 CIDR,拒绝主机位非零、缺前缀和非规范文本;先全部解析成功,再返回不可变候选。不同 IP 版本不会命中。这个“全成才返回”只保护函数边界,不是防火墙事务,更不能证明真实请求已获准通过。

# Python 3.9+,标准库离线实验;不联网,不读取凭据或改防火墙。
import ipaddress


def 校验候选(数据):
    if not isinstance(数据, dict):
        raise ValueError("顶层必须为对象")
    列表 = 数据.get("hooks")
    if not isinstance(列表, list) or not 列表:
        raise ValueError("hooks必须为非空列表")
    候选 = []
    for 条目 in 列表:
        if not isinstance(条目, str) or "/" not in 条目 or "%" in 条目:
            raise ValueError("每项必须为CIDR字符串")
        网段 = ipaddress.ip_network(条目, strict=True)
        if str(网段) != 条目:
            raise ValueError("必须使用规范CIDR")
        候选.append(网段)
    return tuple(候选)  # 全部成功才返回,不修改旧规则。


def 地址命中(地址, 规则):
    if not isinstance(地址, str) or "%" in 地址:
        raise ValueError("必须为不含区域标识的IP字符串")
    来源 = ipaddress.ip_address(地址)
    return any(来源.version == 网段.version and 来源 in 网段 for 网段 in 规则)


if __name__ == "__main__":
    # 下列均为文档网段,绝不是GitHub真实地址。
    旧规则 = 校验候选({"hooks": ["192.0.2.0/24"]})
    新候选 = 校验候选({"hooks": ["192.0.2.0/24", "2001:db8::/32"]})
    检查 = [地址命中("192.0.2.10", 新候选),
            地址命中("2001:db8::1", 新候选),
            not 地址命中("198.51.100.1", 新候选)]
    try:
        校验候选({"hooks": ["192.0.2.0/24", "非法网段"]})
    except ValueError:
        检查.append(tuple(map(str, 旧规则)) == ("192.0.2.0/24",))
    if len(检查) != 4 or not all(检查):
        raise RuntimeError("自检失败")
    print("离线自检:4项通过;无生产变更")

实际标准输出为“离线自检:4项通过;无生产变更”。独立扩展测试还覆盖空值、非法类型、非规范 CIDR、IPv4/IPv6 边界、精确单地址、范围外和后项失败保留旧值。代码不联网,不读取真实凭据,不启动接收端。它验证的是解析和集合判断,不是 GitHub 投递成功率。

五、来源 IP 是准入条件,不是 webhook 身份

直接接收连接时,网络层看到的是连接对端;经过 CDN、负载均衡或反向代理后,应用看到的可能只是代理地址。应在能识别真实连接来源的边界检查,或仅接受受信代理写入并清洗的来源信息。不能盲信任意客户端传入的 X-Forwarded-For 第一项,后端还应防止被绕过代理直连。

即便来源命中 hooks,也不代表某个仓库、组织或 webhook 的合法身份。还要用独立保管的 webhook secret,对收到的原始 body 字节计算 HMAC-SHA256,按 sha256= 格式核对 X-Hub-Signature-256,比较使用 hmac.compare_digest。不要把 JSON 解析后重新序列化再验签,空白或编码改变也会影响签名。

缺少签名或签名不匹配应拒绝;密钥不写进 URL、仓库或日志。传输使用 HTTPS,保持证书验证开启。验签成功后还要检查订阅事件、action 与业务对象权限,不让合法签名成为执行任意业务动作的通行证。

六、十秒内返回成功,也可能只是“收到”

官方要求接收端在十秒内返回 2xx,超时会被视为投递失败。不要在同步请求里做构建或部署等长任务。建议验签、检查事件并可靠持久化接收记录后尽快响应,再异步处理;队列写入失败时不能提前返回成功,否则发送方看到成功,任务却没有落地。

X-GitHub-Delivery 可用于追踪和去重,官方明确说明人工重投仍使用原投递 ID。因此去重不能只做一个永不删除的“见过”集合。至少区分处理中、已成功、可重试失败,失败记录保留重试入口;已成功的重复投递避免重复副作用,处理中记录需要可恢复的超时机制。投递 ID 本身也不能替代签名验证。

恢复后应核对漏接事件并按需重投,再确认消费者完成业务。2xx 是 HTTP 接收结果,不能代替入队、消费和最终业务状态。

七、用证据把故障分层

现象优先证据下一步
超时,应用无记录边界拒绝与代理访问日志核对来源、hooks 和实际规则
到达应用但验签失败签名状态、原始 body 处理路径查密钥及中间层是否改写,不记录密钥
响应 2xx,业务没动作持久接收记录、队列与消费状态查丢失、失败重试和事件过滤
重投仍无动作同一投递 ID 的状态与副作用记录查失败是否被永久去重

排查时只保存必要标识和错误分类,避免把完整业务载荷复制进工单。一个投递的证据链应能串起边界、接收和消费,而不是只有一张“接口返回 200”的截图。

八、上线前后分别验收

  • 名单:确认来源是当前 Meta 的 hooks;候选全量校验通过,差异已审核,失败保留旧值并告警。
  • 边界:检查各实际入口的生效规则与真实来源链,不能只看候选文件,也不能靠伪造请求头验收。
  • 身份:在授权测试环境验证正确签名可接收、缺失或篡改签名被拒绝;HTTPS 证书验证不关闭。
  • 业务:用有权限的真实测试事件核对接收、持久化、消费及副作用;再测失败恢复和同 ID 重投,不能只看 2xx。

本次完成的是本地离线代码实验与官方资料核对,未执行真实 GitHub 投递、生产防火墙变更或队列验收。处理这次变化的重点不是多抄三个网段,而是让名单更新有失败保护,让每一层成功都有对应证据。

参考资料(截至 2026-10-30 核对):GitHub 10 月 29 日公告;Meta API;Webhook 最佳实践;投递签名验证。

🔕 评论已关闭