webhook

package
v0.38.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 8, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package webhook server 侧 webhook 验签原语族:验证器接口 + HMAC 家族 (sha256/384/512 多算法、hex/base64 多编码、可选前缀、header 回退链) + 时间戳防重放 HMAC + 静态令牌 + 回退组合子 + per-platform 注册表 (#270)。收编自 go-git-platform provider/webhook.go 的五个验签器形态 (GitHub sha256=<hex> 双形态 / Forgejo·Gitea 双 header 回退 / GitCode 签名+静态令牌双模 / Gitee 时间戳+密钥 HMAC-base64 含时间漂移防重放 / 任意算法任意 header 的泛型形态),下游可整文件删除私有实现。

实现纪律(每条都是现场教训,逐条搬进源码):

  1. **常量时间比较**:全部签名/令牌比对走 crypto/hmac.Equal——普通 bytes.Equal 有时序侧信道,攻击者可逐字节爆破签名;
  2. **空密钥 fail-closed**:构造期空 secret 直接 panic(「空密钥使 HMAC 验证对可预测载荷形同虚设」)——与 StaticToken 同口径,绝无 「没配密钥就静默放行」的降级路径;
  3. **签名解码双形态**:GitHub 带 sha256= 前缀、部分平台裸 hex/base64 ——剥离可配前缀(大小写不敏感)后统一解码;
  4. **双 header 回退**:分叉平台的 header 约定漂移(Forgejo 老版本用 Gitea 的 header)——header 链按序消歧,「谁在用谁」的约定写进 平台构造器;
  5. **多模消歧的优先序**:签名模式优先于密码模式(Fallback 组合子的 语义保证:签名 header 在场即裁决、错签硬失败不降级;仅当签名 header 缺席才落到密码/静态令牌模式)——防同 secret 交叉认证。

错误语义(errors.Is 判定):

  • ErrMissingSignature:验签 header 整体缺席——Fallback 据此落下一 个候选模式,调用方一般视作「非本平台/未配签名」拒绝;
  • ErrBadSignature:签名在场但不匹配(含解码失败)——硬失败;
  • ErrStaleTimestamp:时间戳缺席/非法/超出容忍窗——硬失败(防重放);
  • ErrUnknownPlatform:注册表查无此平台。

本包只做「验」,不读请求体、不判 IP 白名单、不解析事件类型——服务端 已把 body 读成 []byte 后传进来(框架各异,读体不是原语的职责)。

Index

Constants

View Source
const DefaultMaxDrift = time.Hour

DefaultMaxDrift 时间戳容忍窗缺省(Gitee 官方文档:与请求调用时间误差 不能超过 1 小时)。

Variables

View Source
var (
	// ErrMissingSignature 验签 header 整体缺席。Fallback 组合子据此落到
	// 下一个候选模式;到达调用方时一般视作「非本平台/未配签名」拒绝。
	ErrMissingSignature = errors.New("webhook: 验签 header 缺失")
	// ErrBadSignature 签名在场但不匹配(含前缀不符、编码解码失败)——
	// 硬失败,绝不降级到其他模式(防同 secret 交叉认证)。
	ErrBadSignature = errors.New("webhook: 签名不匹配")
	// ErrStaleTimestamp 时间戳缺席、非法或超出容忍窗——防重放硬失败。
	ErrStaleTimestamp = errors.New("webhook: 时间戳缺失/非法/超出容忍窗")
	// ErrUnknownPlatform 注册表查无此平台。
	ErrUnknownPlatform = errors.New("webhook: 平台未注册")
)

验签错误哨兵(errors.Is 判定;语义见包文档)。

Functions

This section is empty.

Types

type HMACOption

type HMACOption func(*HMACValidator)

HMACOption HMACValidator 的构造选项(仓规禁裸 Option)。

func WithBase64

func WithBase64() HMACOption

WithBase64 签名值按 base64(std)解码;缺省 hex。Gitee 家族是 base64、 GitHub/Forgejo/Gitea/GitCode 家族是 hex——按平台约定显式声明。

func WithFallbackHeaders

func WithFallbackHeaders(names ...string) HMACOption

WithFallbackHeaders 主 header 缺席时的回退 header(按序消歧——分叉平台 的 header 约定漂移:Forgejo 老版本用 Gitea 的 header,「谁在用谁」由 调用方按序声明)。重复声明主 header 不去重(无害:命中即停)。

func WithSignaturePrefix

func WithSignaturePrefix(prefix string) HMACOption

WithSignaturePrefix 签名值前缀(如 GitHub 的 "sha256="):比对时大小写 不敏感地剥离;前缀在场但不匹配时**不剥离**、交由裸解码失败拒绝—— fail-closed(sha1= 的旧签名喂给 sha256 验签器必须死,不能当裸 hex 活)。

type HMACValidator

type HMACValidator struct {
	// contains filtered or unexported fields
}

HMACValidator 泛型 HMAC 验签器:签名值 = enc(hmac(algo, key=secret, message=请求体)),按序在 header 链上取第一个在场的值。

func NewForgejoValidator

func NewForgejoValidator(secret []byte) *HMACValidator

NewForgejoValidator Forgejo 形态:X-Forgejo-Signature,回退老分叉沿用的 X-Gitea-Signature——同签名格式(裸 hex HMAC-SHA256),谁的 header 在场 裁谁。

func NewGitHubValidator

func NewGitHubValidator(secret []byte) *HMACValidator

NewGitHubValidator GitHub 形态:X-Hub-Signature-256,值 sha256=<hex> (前缀大小写不敏感剥离)或裸 hex 双形态,HMAC-SHA256 常量时间比对。

func NewGiteaValidator

func NewGiteaValidator(secret []byte) *HMACValidator

NewGiteaValidator Gitea 形态:X-Gitea-Signature,裸 hex HMAC-SHA256。

func NewHMACValidator

func NewHMACValidator(secret []byte, algo crypto.Hash, header string, opts ...HMACOption) *HMACValidator

NewHMACValidator 构造泛型 HMAC 验签器。

secret 为空是构造期配置错误且**安全错误**:**空密钥使 HMAC 验证对可预测 载荷形同虚设**——panic fail-closed(#270 纪律第 2 条)。algo 支持 crypto.SHA256/crypto.SHA384/crypto.SHA512(sha1/sha224 仅存量平台兼容 用),算法白名单外的值非法 panic;header 为空串非法 panic。

func (*HMACValidator) Validate

func (v *HMACValidator) Validate(r *http.Request, body []byte) error

Validate 按 header 链取首个在场签名,剥前缀、解码、常量时间比对。

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry per-platform 验签器注册表:按平台名路由到对应 Validator。 请求进来先按平台标识(header / URL 路径段 / 配置路由,由服务端自行 判定)取验证器,再裁决。

func NewRegistry

func NewRegistry() *Registry

NewRegistry 创建空注册表。

func (*Registry) Platforms

func (rg *Registry) Platforms() []string

Platforms 已注册平台名的有序快照(整体拷贝出口,经 SafeMap.Snapshot 锁内拷贝、锁外交付;调试转储「现在注册了哪些平台」一行完成)。

func (*Registry) Register

func (rg *Registry) Register(platform string, v Validator)

Register 注册/替换平台的验证器(后写生效——轮换签名密钥时以同名替换 平滑热更)。platform 空串或 validator 为 nil 是构造期配置错误,panic (fail-fast,仓规总则①)。

func (*Registry) Validate

func (rg *Registry) Validate(platform string, r *http.Request, body []byte) error

Validate 按平台路由验签。平台未注册返回带平台名的 ErrUnknownPlatform (errors.Is 可判定)——「没注册」是服务端配置/路由问题,与签名对错 分属两类,不得混报 ErrBadSignature。

func (*Registry) Validator

func (rg *Registry) Validator(platform string) (Validator, bool)

Validator 取平台验证器(不存在返回 false——调用方自行决定拒绝语义; 要统一错误用 Validate)。

type StaticTokenValidator

type StaticTokenValidator struct {
	// contains filtered or unexported fields
}

StaticTokenValidator 静态令牌验签:比对 header 值与配置令牌的**字面 相等**(常量时间)——Gitee/GitCode 密码模式、简单平台的固定令牌形态。 与 HMAC 签名模式的分工由 Fallback 组合子裁决(签名优先)。

func NewStaticTokenValidator

func NewStaticTokenValidator(header string, token []byte) *StaticTokenValidator

NewStaticTokenValidator 构造静态令牌验签器。

token 为空是构造期配置错误且**安全错误**:空令牌等于「带着验签器放行 一切」——panic fail-closed(与 HMAC 空 secret 同口径,仓规总则①)。

func (*StaticTokenValidator) Validate

func (v *StaticTokenValidator) Validate(r *http.Request, body []byte) error

Validate 常量时间比对 header 值与配置令牌。

type TimestampHMACValidator

type TimestampHMACValidator struct {
	// contains filtered or unexported fields
}

TimestampHMACValidator 时间戳参与 HMAC 的防重放验签器。

func NewTimestampHMACValidator

func NewTimestampHMACValidator(secret []byte, algo crypto.Hash, tsName, sigName string, opts ...TimestampOption) *TimestampHMACValidator

NewTimestampHMACValidator 构造时间戳防重放 HMAC 验签器。

缺省口径 = Gitee 官方文档(WebHook 密钥验证和验证算法):

message = timestamp + "\n" + secret
签名值  = base64(HMAC-SHA256(key=secret, message))
容忍窗  = DefaultMaxDrift(1h);timestamp 自动识别秒/毫秒

secret 为空 panic fail-closed(同 HMAC 家族);tsName/sigName 空串 panic。algo 白名单同 NewHMACValidator。

func (*TimestampHMACValidator) Validate

func (v *TimestampHMACValidator) Validate(r *http.Request, body []byte) error

Validate 裁决:签名与时间戳都取到才进入判定,缺任一让位给密码模式 (ErrMissingSignature);都取到则时间窗、签名逐一判死。

type TimestampOption

type TimestampOption func(*TimestampHMACValidator)

TimestampOption TimestampHMACValidator 的构造选项。

func WithClock

func WithClock(now func() time.Time) TimestampOption

WithClock 时钟注入(假时钟确定性测试;与 cache.RunWithClock 同纪律)。

func WithMaxDrift

func WithMaxDrift(d time.Duration) TimestampOption

WithMaxDrift 时间戳容忍窗(必须 >0,防重放窗不存在即失去本验签器的存在 意义——非法值 panic 而非静默关闭校验)。

func WithMessage

func WithMessage(fn func(timestamp string, body []byte) []byte) TimestampOption

WithMessage 自定义签名 message 组装(缺省 timestamp+"\n"+secret,Gitee 官方口径;body 参与签名的平台在此注入,如 timestamp+body)。

func WithQuerySource

func WithQuerySource() TimestampOption

WithQuerySource 时间戳与签名改从 **URL query 参数**取(Gitee 签名形态 之一:timestamp 与 sign 拼在回调 URL 上)。缺省走 header。

func WithURLEncoded

func WithURLEncoded() TimestampOption

WithURLEncoded 签名值经 URL 编码(Java URLEncoder.encode 形态:base64 的 +/= 被转 %2B/%2F/%3D)——比对前先做 PathUnescape 解一层。

type Validator

type Validator interface {
	Validate(r *http.Request, body []byte) error
}

Validator 单个 webhook 验签器。

r 是已收到的请求(验签器只读 header 与 URL,不读 body);body 是服务端 已读出的**原始**请求体字节(框架各异,读体不是原语的职责)。实现必须 并发安全,且全部比对走常量时间(crypto/hmac.Equal)。

func Fallback

func Fallback(validators ...Validator) Validator

Fallback 按序尝试多个验证器的回退组合子——多平台 header 回退与多模 (签名/密码)消歧的单一实现。

裁决语义(多模消歧的优先序,#270 现场纪律第 5 条):

  • 候选返回 ErrMissingSignature(它的验签 header 缺席)→ **落下一个 候选**——「该模式不在场」不是失败,是让位;
  • 候选返回**任何其他错误**(ErrBadSignature/ErrStaleTimestamp/自定义) → **立即整体失败**,不再尝试后续候选——签名 header 在场即裁决, 错签硬失败不降级:把签名 header 换成伪造的静态令牌 header 不能 「降级」洗白,同 secret 交叉认证被优先序挡死;
  • 全部候选都 ErrMissingSignature → 返回 ErrMissingSignature。

空候选列表是构造期配置错误,panic(仓规总则①)。候选内的 nil 元素 同样 panic——静默跳过会把「配了个空模式」藏成永远让位。

func NewGitCodeValidator

func NewGitCodeValidator(secret []byte, staticToken []byte) Validator

NewGitCodeValidator GitCode 形态:X-GitCode-Signature,回退老分叉沿用的 X-Gitea-Signature(裸 hex HMAC-SHA256)。**签名与静态令牌双模并存**: 再回退 X-GitCode-Token 静态令牌比对——签名模式优先(Fallback 语义: 签名 header 在场即裁决,错签硬失败),令牌为空即双模退化为纯签名。

func NewGiteeValidator

func NewGiteeValidator(secret []byte, password []byte, opts ...TimestampOption) Validator

NewGiteeValidator Gitee 形态:签名模式(X-Gitee-Timestamp + X-Gitee-Token, base64(HMAC-SHA256(key=secret, message=timestamp+"\n"+secret)),容忍窗 缺省 1h 防重放)优先;**无时间戳时**按密码模式消歧(X-Gitee-Token 为 密码明文,常量时间比对)。

AllowPasswordMode 显式 opt-in:密码为空即纯签名模式;密码非空才组合 密码候选——「默认拒绝密码模式」写死在构造期(密码模式只证明「知道 密码」,不绑定报文完整性,能不用就不用)。

type ValidatorFunc

type ValidatorFunc func(r *http.Request, body []byte) error

ValidatorFunc 函数适配器——让普通函数当验证器(如调用方先做平台路由 再委托既有校验逻辑)。

func (ValidatorFunc) Validate

func (f ValidatorFunc) Validate(r *http.Request, body []byte) error

Validate 实现 Validator。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL