webhook

package
v0.50.0 Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package webhook webhook 签约原语族:「收」(验签)与「发」(签名)两侧 的字段约定单源。

收侧(验签原语族,#270)

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

服务端把 body 读成 []byte 后传进来(框架各异,读体不是原语的职责):

v := webhook.NewGitHubValidator(secret)
if err := v.Validate(r, body); err != nil { ... }

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

  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:注册表查无此平台。

发侧(签名原语族,#325/#328)

验签族的对称面:任何往外发 webhook 的服务(事件转发器、平台对端)与 每个测 webhook 接收的测试夹具,都不必再手算 HMAC——签名 message 形态 (hmacSum)、前缀/编码(encodeSignature,与收侧 decodeSignature 镜像)、 平台 header 名与收侧共用同一份实现,「发」与「收」的字段约定没有第二源, 跨服务对接时两侧字段对不上的事故面从源头消除。

HMACSigner 是 HMACValidator 的逐参数对偶(同 secret 同选项下互逆); TimestampHMACSigner 是 TimestampHMACValidator 的对偶(发出时打时间戳); SignHMAC / SignTimestampHMAC 是两条纯函数 helper(测试夹具一行替代手写 hmac 块);NewEventID + EventEnvelope 给出幂等事件 ID 与标准事件信封的 推荐形态。边界哲学与收侧一致:Sign 只产出可直接套上出站请求的 header 集、 信封只产出 payload 字节,HTTP 发送归调用方——纯函数零状态,不读体不发送。

发出侧 ↔ 收侧配对表(同 secret 下 Sign 喂 Validate 必过,配对测试钉住):

发出侧                            收侧
NewGitHubStyleSigner              NewGitHubValidator
NewForgejoSigner                  NewForgejoValidator
NewGiteaSigner                    NewGiteaValidator
NewGitCodeSigner                  NewGitCodeValidator
NewGiteeSigner                    NewGiteeValidator
SignHMAC / NewHMACSigner          NewHMACValidator(同组选项同配)
SignTimestampHMAC / NewTimestampHMACSigner  NewTimestampHMACValidator

注意事项(与收侧纪律成对):secret 空串同样 panic fail-closed(无密钥 签名等于裸奔);前缀/编码/message 组装在两侧必须**同配**(发侧带 sha256= 前缀、收侧没配剥前缀 = 无故拒绝,配对表与同组选项就是为了把 这类联调事故挡在构造期);SignerWith* 是发侧选项前缀,与收侧 With* 一一对应成对。

转发面(游标式 at-least-once 批量转发器,#482)

审计/事件外发到 SIEM 的「取批 → 全 sink 投递 → 推进游标」不变量单源: Forwarder 机制只收不变量,游标存储(CursorStore,宿主的设置表/持久 KV)与批源(BatchSource,如 WHERE seq > ? ORDER BY seq LIMIT ?)由宿主 注入,本包不假设任何存储形态;内置 MemoryCursorStore(契约测试与骨架 用)与两个 sink——WebhookSink(整批一个 POST,body 为载荷 JSON 数组, 可选 HMAC 签名,复用发侧 Signer——签的就是发出的 body 字节)与 SyslogSink(RFC5424 一事件一帧,逐轮连接断链下轮重试)。

at-least-once 语义(宁重不丢):游标只在**全部 sink 成功且游标落库 成功**后推进到本批最大 Seq;任一失败游标不动、下轮重取同窗重发**整批** ——重投的最小单位是一整批(sink A 成功 B 失败的那轮,A 下轮收到重复 事件是有意为之);先投递后记账的顺序保证「丢」只可能表现为「重」。 接收端必须幂等:按载荷内稳定事件 ID(推荐 EventEnvelope.ID / 载荷自有 id,或签名头的 DefaultDeliveryHeader 投递 ID)在解包/落库之前去重; 重投只重复不乱序,不得依赖「恰好一次」。毒批(非法 payload/坏批序) fail-loud 卡住游标——跳过=有意丢数据,是宿主的决策不是转发器的。

RFC5424 首版承诺:PRI + 版本 1 + 时间戳(UTC 毫秒)+ 主机 + APP-NAME + MSG,STRUCTURED-DATA 恒 NILVALUE;分帧用非透明式(行=一帧,MSG 内换行 净化为空格),octet-counting/TLS 不在首版。

本包做「签」「验」「转发」三面的字段约定单源;不读请求体/不判 IP 白名单 /不解析事件类型/不做重试退避——时间戳防重放窗的判定在收端(发出端只按 约定打时间戳与幂等 ID,消费端按 DefaultDeliveryHeader 去重),转发面的 轮询节奏与告警归宿主。

Index

Constants

View Source
const (
	// DefaultEventHeader 事件类型头(Sign 在配置了事件类型时写入)。
	DefaultEventHeader = "X-Webhook-Event"
	// DefaultDeliveryHeader 幂等投递 ID 头(Sign 写入,值形如
	// <producer>-<event>-<uuidv4>,见 NewEventID)。
	DefaultDeliveryHeader = "X-Webhook-Delivery"
	// DefaultTimestampHeader 发出时间戳头(SignerWithTimestampHeader 开启
	// 时写入,unix 秒;收端可用 NewHMACValidator 同头自取或另行校验防重放窗)。
	DefaultTimestampHeader = "X-Webhook-Timestamp"
)

发出侧推荐约定头名(#328 设计点 2):通用发端服务用推荐名(与 GitHub 的 X-GitHub-Event/X-GitHub-Delivery 同风格),平台历史名随平台构造器走。 验签侧没有这些头的既定约定,用泛型验证器按名取即可。

View Source
const DefaultForwardBatchLimit = 500

DefaultForwardBatchLimit 每轮最大取批条数缺省值——单轮外发的扇出上限 (批越大重投窗口越大,接收端幂等窗口需与之匹配)。

View Source
const DefaultMaxDrift = time.Hour

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

View Source
const DefaultSyslogDialTimeout = 5 * time.Second

DefaultSyslogDialTimeout syslog 连接超时缺省值——5s(issue 原型 DialTimeout 口径,探测类上限内)。断链不是终态也不重试连接:连接复用 是性能优化不是正确性要件,下一轮转发循环自然重试。

View Source
const DefaultSyslogWriteTimeout = 30 * time.Second

DefaultSyslogWriteTimeout 写帧整体死线缺省值——30s(治理类上限内): 单批至多数百帧,慢链路重传需要余量,但不得挂死转发循环。

View Source
const DefaultWebhookTimeout = 10 * time.Second

DefaultWebhookTimeout webhook sink 单次投递整体超时缺省值——探测类 上限内(仓规「超时默认值」);断链不重试(重试归转发循环:游标不动 下轮自然重发)。

Variables

View Source
var (
	// ErrDeliveryFailed sink 投递失败(HTTP 非 2xx / 网络错 / 签名失败 /
	// syslog 连接或写帧失败)——游标不推进,整批下轮重发。
	ErrDeliveryFailed = errors.New("webhook: 转发投递失败")
	// ErrInvalidForwardPayload 批内载荷非法(空或非合法 JSON)——毒批
	// fail-loud 卡住游标,绝不静默跳过(跳过=有意丢数据,是宿主的决策
	// 不是转发器的;宿主需在源头保证产出质量或显式改游标绕过)。
	ErrInvalidForwardPayload = errors.New("webhook: 转发载荷非法")
	// ErrBadForwardBatch 批序非法(Seq 非严格递增或未越过游标)——源侧
	// 契约破坏,硬失败不推进(推进非最大 Seq 会静默丢事件)。
	ErrBadForwardBatch = errors.New("webhook: 转发批次序非法")
)

转发面错误哨兵(errors.Is 判定)。

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

func FormatRFC5424 added in v0.49.0

func FormatRFC5424(m RFC5424Message) string

FormatRFC5424 构造一帧 RFC5424 行文本(不含行尾换行——帧终止符归 sink 的传输面):

<PRI>1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID - MSG

PRI = facility*8 + severity(issue 原型 <86> 即 authpriv(10).info(6))。 规则:

  • facility/severity 越界 panic——PRI 是协议值,越界帧会被 SIEM 解析 面拒收,产出期就死好过发出垃圾(fail-fast,仓规总则①);
  • 空串 Hostname/AppName/ProcID/MsgID 与空 Message → NILVALUE "-";
  • Timestamp 归一 UTC 毫秒精度;零值 → NILVALUE "-";
  • Message 内 \r \n 替换为空格:本 sink 用「非透明分帧」(行 = 一帧, 帧尾 \n 由 sink 补),MSG 内换行会撕开分帧——有损替换是有意取舍 (octet-counting 分帧不在首版;要无损请用 WebhookSink 的 JSON 面)。

func NewEventID added in v0.42.0

func NewEventID(producer, event string) (string, error)

NewEventID 推荐幂等事件 ID 形态(#328 设计点 2):<producer>-<event>- <uuidv4>(空段跳过——没有 producer 就是 <event>-<uuid>)。uuidv4 取自 crypto/rand(pkg 零第三方依赖,不引 uuid 库)。随机源失败显式上抛 (仓规总则③)——降级成时间戳 ID 会让重试窗口内的重发无法被识别为同一次投递。

func SignHMAC added in v0.42.0

func SignHMAC(secret []byte, algo crypto.Hash, payload []byte, opts ...HMACOption) string

SignHMAC 发端 helper(#325):与 NewHMACValidator 同语义面的纯函数形态 ——返回可直接塞 header 的签名值,前缀/编码由同一组 HMACOption 驱动(与 验签器共用选项值,无需另记一套)。测试夹具与一次性签名用;要附带 时间戳/事件/幂等 ID 的完整发出约定用 HMACSigner。

func SignTimestampHMAC added in v0.42.0

func SignTimestampHMAC(secret []byte, algo crypto.Hash, ts string) string

SignTimestampHMAC 发端 helper(#325):与 NewTimestampHMACValidator 缺省 口径同语义面——Gitee 形态 base64(hmac(algo, key=secret, msg=ts+"\n"+secret))。与 giteeMessage 共享 message 组装,天然不漂移。

Types

type BatchSource added in v0.49.0

type BatchSource interface {
	FetchAfter(ctx context.Context, cursor uint64, limit int) ([]ForwardEvent, error)
}

BatchSource 批源接口:按游标取一批待外发事件。契约(Forwarder 校验, 破坏即 ErrBadForwardBatch 硬失败):只返回 Seq > cursor 的事件、按 Seq 严格升序、至多 limit 条。宿主查询形如 WHERE seq > ? ORDER BY seq ASC LIMIT ?。

type BatchSourceFunc added in v0.49.0

type BatchSourceFunc func(ctx context.Context, cursor uint64, limit int) ([]ForwardEvent, error)

BatchSourceFunc 函数适配器——让普通查询函数当批源。

func (BatchSourceFunc) FetchAfter added in v0.49.0

func (f BatchSourceFunc) FetchAfter(ctx context.Context, cursor uint64, limit int) ([]ForwardEvent, error)

FetchAfter 实现 BatchSource。

type CursorStore added in v0.49.0

type CursorStore interface {
	LoadCursor(ctx context.Context) (uint64, error)
	SaveCursor(ctx context.Context, cursor uint64) error
}

CursorStore 游标存储接口:宿主注入(业务仓的 bq_setting 表、任何持久 KV)。实现必须并发安全,或由宿主保证串行调用。契约:无游标时 LoadCursor 返回 0(首次全量外发起点)而非 error——「还没有」与「读 失败」是两件事,混报会把首启伪装成故障。

type EventEnvelope added in v0.42.0

type EventEnvelope struct {
	Event     string          `json:"event"`
	ID        string          `json:"id"`
	Timestamp int64           `json:"timestamp"` // Unix 秒
	Data      json.RawMessage `json:"data"`
}

EventEnvelope 事件信封:Data 内嵌原始 payload(json.RawMessage,字节原 样嵌入不重排序)。字段导出——调用方可直接构造(时间戳可注入固定值做 确定性测试),NewEventEnvelope 是 fail-fast 快捷口。

func NewEventEnvelope added in v0.42.0

func NewEventEnvelope(event, id string, payload []byte) *EventEnvelope

NewEventEnvelope 构造事件信封(Timestamp 取当前时刻)。

event/id 空串、payload 空、payload 不是合法 JSON 均为构造期配置错误, panic fail-fast(信封的 event/id 是消费端路由与幂等去重的键,缺了等于 没信封;payload 非法 JSON 会在消费端炸出难排查的解码错,不如产出期就死)。

func (*EventEnvelope) Bytes added in v0.42.0

func (e *EventEnvelope) Bytes() ([]byte, error)

Bytes 信封的线格式字节(json.Marshal)——签名器与 HTTP 发送吃的就是它。

type Facility added in v0.49.0

type Facility uint8

Facility RFC5424 PRI 的 facility 段(0–23,数值即协议值)。

const (
	FacilityKernel   Facility = 0  // kernel messages
	FacilityUser     Facility = 1  // user-level messages
	FacilityMail     Facility = 2  // mail system
	FacilityDaemon   Facility = 3  // system daemons
	FacilityAuth     Facility = 4  // security/authorization messages
	FacilitySyslog   Facility = 5  // syslogd 内部消息
	FacilityLPR      Facility = 6  // line printer subsystem
	FacilityNews     Facility = 7  // network news subsystem
	FacilityUUCP     Facility = 8  // UUCP subsystem
	FacilityCron     Facility = 9  // clock daemon
	FacilityAuthPriv Facility = 10 // security/authorization(私有口径——审计/SIEM 外发常用)
	FacilityFTP      Facility = 11 // ftp daemon
	FacilityLocal0   Facility = 16 // 本地用途 0
	FacilityLocal1   Facility = 17
	FacilityLocal2   Facility = 18
	FacilityLocal3   Facility = 19
	FacilityLocal4   Facility = 20
	FacilityLocal5   Facility = 21
	FacilityLocal6   Facility = 22
	FacilityLocal7   Facility = 23
)

标准 facility 常量(RFC5424 §6.2.1;12–15 的保留段无常量,数值字面量 自行指定)。

type ForwardEvent added in v0.49.0

type ForwardEvent struct {
	// Seq 单调递增游标序号。源侧契约:FetchAfter 只回 Seq > cursor 的
	// 事件且按 Seq 升序(转发器校验,破坏即 ErrBadForwardBatch 不推进
	// ——序号错乱时推进「批尾」会静默丢事件,宁可不发)。Seq 不上
	// webhook 线格式:幂等去重键在载荷自身的稳定 ID(SIEM 侧按 id 去重)。
	Seq uint64
	// Payload 已编码的 JSON 载荷字节(审计 wire event 等;两 sink 的
	// 共同输入:webhook 作 JSON 数组元素原样嵌入,syslog 作 MSG 文本)。
	Payload []byte
}

ForwardEvent 转发批次里的一条事件。

type ForwardSink added in v0.49.0

type ForwardSink interface {
	Forward(ctx context.Context, batch []ForwardEvent) error
}

ForwardSink 转发 sink:把一批事件投递到一个下游。实现约定:

  • 必须容忍整批重投(转发器的 at-least-once 语义——同一批可能被重复 调用多次),接收端幂等或 sink 自身去重;
  • Forward 要么整体成功返回 nil,要么返回非 nil error——「部分成功」 不可表达(error 即「本 sink 本批未完成投递」),已送达的部分由 重投覆盖;
  • 实现必须并发安全,或由宿主保证串行调用。

type Forwarder added in v0.49.0

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

Forwarder 游标式 at-least-once 批量转发器。

at-least-once 语义(宁重不丢)与幂等建议:

  • 推进时机:游标只在**全部 sink 都成功**且**游标落库成功**后才推进 到本批最大 Seq;任何一步失败游标不动,下一轮重新取同窗重发整批;
  • 重投边界:最小重投单位是一整批(不是单条)——sink A 成功、B 失败 的那轮,A 会在下轮收到重复事件,这是有意为之;游标落库失败 (SaveCursor 出错)同样下轮重发,先投递后记账的顺序保证「丢」只 可能表现为「重」,绝不反向丢失;
  • 幂等建议:接收端(SIEM / 对端 webhook 服务)按载荷内的稳定事件 ID 去重(推荐 EventEnvelope.ID / 载荷自有 id;发出侧用 HMACSigner 时签名头自带 DefaultDeliveryHeader 幂等投递 ID,可按其去重), 重复投递在解包/落库之前丢弃;
  • 顺序保证:重投只重复不乱序——批间按游标窗口升序、批内按 Seq 升序;接收端不得依赖「恰好一次」。

func NewForwarder added in v0.49.0

func NewForwarder(store CursorStore, src BatchSource, sinks []ForwardSink, opts ...ForwarderOption) *Forwarder

NewForwarder 构造转发器:store/source 为 nil、sinks 为空或含 nil、 options 含 nil 均是构造期配置错误,panic(fail-fast,仓规总则①)。

func (*Forwarder) ForwardOnce added in v0.49.0

func (f *Forwarder) ForwardOnce(ctx context.Context) (int, error)

ForwardOnce 外发一轮:加载游标 → 取批 → 全 sink 依次投递 → 推进游标。

返回值:成功推进时为本批条数(>0);空批返回 (0, nil)——空批不算 错误,调用方按自己的轮询节奏调度下一轮。任何失败游标都不推进(语义 见 Forwarder 文档),返回的 error 可用 ErrDeliveryFailed / ErrInvalidForwardPayload / ErrBadForwardBatch 哨兵分类。

type ForwarderOption added in v0.49.0

type ForwarderOption func(*Forwarder)

ForwarderOption Forwarder 的构造选项。

func WithForwardBatchLimit added in v0.49.0

func WithForwardBatchLimit(n int) ForwarderOption

WithForwardBatchLimit 每轮最大取批条数(透传给 BatchSource 的 limit)。 n <= 0 是构造期配置错误,panic(fail-fast,仓规总则①)。

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 HMACSigner added in v0.42.0

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

HMACSigner HMACValidator 的发出侧对偶:签名值 = prefix + enc(hmac(algo, key=secret, message=payload)),与 NewHMACValidator 同语义面逐字节互逆 ——同 secret 同选项下 Sign 产出的 header 喂给 Validate 必过(配对表 测试钉住)。可选附带事件类型头、幂等投递 ID 头与时间戳头(推荐约定, 见 SignerWithEvent / SignerWithTimestampHeader)。

func NewForgejoSigner added in v0.42.0

func NewForgejoSigner(secret []byte) *HMACSigner

NewForgejoSigner Forgejo 形态发出侧:X-Forgejo-Signature,裸 hex——与 NewForgejoValidator 同款(收端自带 X-Gitea-Signature 回退,发端只写 主 header)。

func NewGitCodeSigner added in v0.42.0

func NewGitCodeSigner(secret []byte) *HMACSigner

NewGitCodeSigner GitCode 形态发出侧:X-GitCode-Signature,裸 hex——与 NewGitCodeValidator 的签名模式同款。静态令牌模式没有「生成」面(令牌 是明文常量,发出就是 req.Header.Set("X-GitCode-Token", token) 一行), 不设对应构造器。

func NewGitHubStyleSigner added in v0.42.0

func NewGitHubStyleSigner(secret []byte) *HMACSigner

NewGitHubStyleSigner GitHub 形态发出侧:X-Hub-Signature-256,值 sha256=<hex>——与 NewGitHubValidator 同款形态。

func NewGiteaSigner added in v0.42.0

func NewGiteaSigner(secret []byte) *HMACSigner

NewGiteaSigner Gitea 形态发出侧:X-Gitea-Signature,裸 hex——与 NewGiteaValidator 同款。

func NewHMACSigner added in v0.42.0

func NewHMACSigner(secret []byte, algo crypto.Hash, header string, opts ...HMACSignerOption) *HMACSigner

NewHMACSigner 构造泛型 HMAC 签名器——NewHMACValidator 的逐参数对偶。

构造校验与验签器同口径:secret 为空 panic fail-closed(发出的签名没有 密钥等于裸奔,宁可不发);algo 白名单同 NewHMACValidator;header 空串 panic;nil 选项 panic。

func (*HMACSigner) Sign added in v0.42.0

func (s *HMACSigner) Sign(payload []byte) (http.Header, error)

Sign 签出已签 payload 的 header 集:

  • 签名头(构造参数 header):prefix + enc(hmac(algo, key=secret, message=payload))——与 NewHMACValidator 同选项下逐字节互逆;
  • 时间戳头(SignerWithTimestampHeader 开启时):unix 秒;
  • 事件头 + 幂等投递 ID 头(SignerWithEvent 配置时):DefaultEventHeader / DefaultDeliveryHeader,ID 走 NewEventID 推荐形态或 SignerWithDeliveryID。

返回的 header 可直接套到出站请求(h := signed.Header(); req.Header = h 或逐个 req.Header.Set),调用方可在其上继续追加自有头。error 仅在幂等 ID 的随机源失败(crypto/rand 读失败)时非 nil。

type HMACSignerOption added in v0.42.0

type HMACSignerOption func(*HMACSigner)

HMACSignerOption HMACSigner 的构造选项(发出侧选项统一 SignerWith* 前缀, 与验签侧的 With* 一一对应成对)。

func SignerWithBase64 added in v0.42.0

func SignerWithBase64() HMACSignerOption

SignerWithBase64 签名值按 base64(std)编码;缺省 hex——与验签侧 WithBase64 同语义,两侧必须同配。

func SignerWithClock added in v0.42.0

func SignerWithClock(now func() time.Time) HMACSignerOption

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

func SignerWithDeliveryID added in v0.42.0

func SignerWithDeliveryID(fn func() string) HMACSignerOption

SignerWithDeliveryID 自定义投递 ID 生成器(已有分布式 ID 的服务接入用; 缺省走 NewEventID 推荐形态)。fn 返回空串视为配置错误——空 ID 使消费端 幂等去重失效,Sign 时 panic 而非发出。

func SignerWithEvent added in v0.42.0

func SignerWithEvent(event string) HMACSignerOption

SignerWithEvent 声明事件类型:Sign 写入 DefaultEventHeader,并连带写入 DefaultDeliveryHeader 幂等投递 ID。event 为空串是配置错误(事件类型是 幂等 ID 推荐形态的组成部分),panic。

func SignerWithProducer added in v0.42.0

func SignerWithProducer(producer string) HMACSignerOption

SignerWithProducer 幂等投递 ID 升级为推荐完整形态 <producer>-<event>- <uuidv4>(#328 设计点 2)——跨服务对账时 producer 段区分「谁发的」, 同 event 名的服务不会撞 ID。producer 为空串是配置错误,panic。

func SignerWithSignaturePrefix added in v0.42.0

func SignerWithSignaturePrefix(prefix string) HMACSignerOption

SignerWithSignaturePrefix 签名值前缀(如 GitHub 的 "sha256=")——与验签 侧 WithSignaturePrefix 同语义:两侧必须同配,否则验签端剥不掉前缀 (带前缀值喂给无前缀验证器会被裸解码拒绝)。

func SignerWithTimestampHeader added in v0.42.0

func SignerWithTimestampHeader(name string) HMACSignerOption

SignerWithTimestampHeader 开启发出时间戳头并命名(unix 秒;推荐用 DefaultTimestampHeader)。时间戳防重放窗的判定在收端(TimestampHMAC 家族或服务端自校验),发出侧职责是把时间戳按约定打出。name 为空串 panic。

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 为空 panic fail-closed(同 mustSecret,#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 MemoryCursorStore added in v0.49.0

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

MemoryCursorStore 内存游标存储:契约测试与启动期骨架用;进程重启即归 零(从头重发——at-least-once 的重发对幂等接收端是安全的,见 Forwarder 幂等建议),生产用宿主持久化实现。

func NewMemoryCursorStore added in v0.49.0

func NewMemoryCursorStore() *MemoryCursorStore

NewMemoryCursorStore 构造内存游标存储(初值 0)。

func (*MemoryCursorStore) LoadCursor added in v0.49.0

func (s *MemoryCursorStore) LoadCursor(_ context.Context) (uint64, error)

LoadCursor 实现 CursorStore。

func (*MemoryCursorStore) SaveCursor added in v0.49.0

func (s *MemoryCursorStore) SaveCursor(_ context.Context, cursor uint64) error

SaveCursor 实现 CursorStore。

type RFC5424Message added in v0.49.0

type RFC5424Message struct {
	Facility  Facility
	Severity  Severity
	Timestamp time.Time
	Hostname  string
	AppName   string
	ProcID    string
	MsgID     string
	Message   string
}

RFC5424Message 一帧 RFC5424 行的字段。空串字段与零值 Timestamp 由 FormatRFC5424 归一为 NILVALUE "-"(时间未知也是合法 RFC5424 帧)。

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 Severity added in v0.49.0

type Severity uint8

Severity RFC5424 PRI 的 severity 段(0–7,数值即协议值;0 最重)。

const (
	SeverityEmergency     Severity = 0 // system is unusable
	SeverityAlert         Severity = 1 // action must be taken immediately
	SeverityCritical      Severity = 2 // critical conditions
	SeverityError         Severity = 3 // error conditions
	SeverityWarning       Severity = 4 // warning conditions
	SeverityNotice        Severity = 5 // normal but significant
	SeverityInformational Severity = 6 // informational messages
	SeverityDebug         Severity = 7 // debug-level messages
)

标准 severity 常量(RFC5424 §6.2.1)。

type Signer added in v0.42.0

type Signer interface {
	Sign(payload []byte) (http.Header, error)
}

Signer 发出侧签名器接口:Sign 产出已签 payload 的 header 集。实现必须 并发安全(共享 secret 只读);error 仅在幂等 ID 随机源失败时非 nil ——发出方该失败要显式上抛,不能发出去一个空/重复 ID。

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 SyslogSink added in v0.49.0

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

SyslogSink RFC5424 syslog 转发 sink(实现 ForwardSink):每次 Forward 现连现写现断——逐轮连接、断链下轮重试(SIEM 前置 rsyslog/syslog-ng 的 常态是断链重启,长连接保活反而把「对端没了」藏成写超时)。每条事件 一帧,MSG = 载荷 JSON 原文,帧尾 \n(非透明分帧;UDP 每帧一个数据报)。

func NewSyslogSink added in v0.49.0

func NewSyslogSink(network, addr, hostname, appName string, opts ...SyslogSinkOption) *SyslogSink

NewSyslogSink 构造 syslog 转发 sink。addr 为空串是配置错误 panic; network 为空串回退 "tcp"(仓规总则②:空参数=显式回退默认值)。hostname 空串与 appName 空串合法(帧内归 NILVALUE,但审计外发建议都给全—— SIEM 侧按 host/app 分面检索)。

func (*SyslogSink) Forward added in v0.49.0

func (s *SyslogSink) Forward(ctx context.Context, batch []ForwardEvent) error

Forward 每条事件一帧 RFC5424 逐帧写出(实现 ForwardSink)。任一帧写 失败即返回包 ErrDeliveryFailed 的错误——已写出的帧会在下轮整批重发 (at-least-once,接收端按载荷 ID 去重);空载荷返回包 ErrInvalidForwardPayload 的错误(空帧是源侧 bug,不产出占位垃圾)。

type SyslogSinkOption added in v0.49.0

type SyslogSinkOption func(*SyslogSink)

SyslogSinkOption SyslogSink 的构造选项。

func WithSyslogClock added in v0.49.0

func WithSyslogClock(now func() time.Time) SyslogSinkOption

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

func WithSyslogDialTimeout added in v0.49.0

func WithSyslogDialTimeout(d time.Duration) SyslogSinkOption

WithSyslogDialTimeout 连接超时。d <= 0 panic(用缺省值请不配置本选项, nil 参数=回退默认是 nil 选项的事,显式给了就必须合法)。

func WithSyslogFacility added in v0.49.0

func WithSyslogFacility(f Facility) SyslogSinkOption

WithSyslogFacility PRI facility 段(缺省 FacilityAuthPriv——审计/SIEM 外发常用口径,对应 PRI 80–87)。越界 panic。

func WithSyslogMsgID added in v0.49.0

func WithSyslogMsgID(id string) SyslogSinkOption

WithSyslogMsgID MSGID 段(缺省 NILVALUE;如事件类型名)。

func WithSyslogProcID added in v0.49.0

func WithSyslogProcID(id string) SyslogSinkOption

WithSyslogProcID PROCID 段(缺省 NILVALUE;如进程 PID、实例号)。

func WithSyslogSeverity added in v0.49.0

func WithSyslogSeverity(v Severity) SyslogSinkOption

WithSyslogSeverity PRI severity 段(缺省 SeverityInformational——issue 原型 <86> 即 authpriv.info)。越界 panic。

func WithSyslogWriteTimeout added in v0.49.0

func WithSyslogWriteTimeout(d time.Duration) SyslogSinkOption

WithSyslogWriteTimeout 写帧整体死线。d <= 0 panic。

type TimestampHMACSigner added in v0.42.0

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

TimestampHMACSigner TimestampHMACValidator 的发出侧对偶:签名值 = base64(hmac(algo, key=secret, message)),message 缺省按 Gitee 官方口径 timestamp+"\n"+secret 组装(body 参与签名的平台用 SignerWithMessage 注入自己的组装,与验签侧 WithMessage 一一对应)。

func NewGiteeSigner added in v0.42.0

func NewGiteeSigner(secret []byte, opts ...TimestampSignerOption) *TimestampHMACSigner

NewGiteeSigner Gitee 形态发出侧:X-Gitee-Timestamp(毫秒)+ X-Gitee-Token (base64(HMAC-SHA256(key=secret, message=timestamp+"\n"+secret)))——与 NewGiteeValidator 的签名模式同款。opts 透传 SignerWithMessage / TimestampSignerWithClock / SignerWithURLEncoded(须与收端同配;#392: 时钟选项实名是 TimestampSignerWithClock——SignerWithClock 是 HMACSigner 家族的选项,传给本构造器类型错误,照旧文档写编译不过)。

func NewTimestampHMACSigner added in v0.42.0

func NewTimestampHMACSigner(secret []byte, algo crypto.Hash, tsName, sigName string, opts ...TimestampSignerOption) *TimestampHMACSigner

NewTimestampHMACSigner 构造时间戳防重放 HMAC 签名器——NewTimestampHMAC Validator 的逐参数对偶。构造校验同口径:secret 为空 panic fail-closed、 algo 白名单、tsName/sigName 空串 panic、nil 选项 panic。

func (*TimestampHMACSigner) Sign added in v0.42.0

func (s *TimestampHMACSigner) Sign(payload []byte) (http.Header, error)

Sign 签出带时间戳的 header 集:时间戳头 = 当前毫秒时间戳(Gitee 官方 13 位口径),签名头 = base64(hmac(algo, key=secret, message))。message 缺省不含 body(与验签侧缺省一致——Gitee 口径密钥+时间戳即信物;body 参与签名的平台走 SignerWithMessage)。error 恒为 nil(本签名器无随机 成分),为与 Signer 接口同形保留。

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 TimestampSignerOption added in v0.42.0

type TimestampSignerOption func(*TimestampHMACSigner)

TimestampSignerOption TimestampHMACSigner 的构造选项。

func SignerWithMessage added in v0.42.0

func SignerWithMessage(fn func(timestamp string, body []byte) []byte) TimestampSignerOption

SignerWithMessage 自定义签名 message 组装(与验签侧 WithMessage 同语义 同签名——两侧必须用同一个组装函数,否则验签端算出的 message 对不上)。

func SignerWithURLEncoded added in v0.42.0

func SignerWithURLEncoded() TimestampSignerOption

SignerWithURLEncoded 签名值 URL 编码(与验签侧 WithURLEncoded 同语义: Java URLEncoder 形态的接收端先解一层再比对——两侧必须同配)。

func TimestampSignerWithClock added in v0.42.0

func TimestampSignerWithClock(now func() time.Time) TimestampSignerOption

TimestampSignerWithClock 时钟注入(假时钟确定性测试;与验签侧 WithClock 同纪律。与泛型签名器的 SignerWithClock 是两个选项类型,名字带全量前缀消歧)。

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。

type WebhookSink added in v0.49.0

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

WebhookSink webhook 转发 sink:整批一个 POST,body 为载荷 JSON 数组 (json.RawMessage 原样嵌入,不重排序不重编码),Content-Type application/json;可选 HMAC 签名(复用发侧 Signer 家族——签的就是 发出的 body 字节,与收侧验证器同约定互逆)。

func NewWebhookSink added in v0.49.0

func NewWebhookSink(url string, opts ...WebhookSinkOption) *WebhookSink

NewWebhookSink 构造 webhook 转发 sink。url 为空串是配置错误,panic (fail-fast,仓规总则①)。

func (*WebhookSink) Forward added in v0.49.0

func (w *WebhookSink) Forward(ctx context.Context, batch []ForwardEvent) error

Forward 整批一个 POST(实现 ForwardSink):body = 载荷 JSON 数组(元素 字节原样嵌入),Content-Type application/json;配置了签名器时按 Sign 产出套签名/事件/投递 ID 头。2xx 视为投递成功;非 2xx 与网络错均返回 包 ErrDeliveryFailed 的错误——游标不推进,整批下轮重发。

type WebhookSinkOption added in v0.49.0

type WebhookSinkOption func(*WebhookSink)

WebhookSinkOption WebhookSink 的构造选项。

func WithWebhookClient added in v0.49.0

func WithWebhookClient(c *http.Client) WebhookSinkOption

WithWebhookClient 自定义 HTTP 客户端(代理/连接池/细粒度超时策略归 调用方)。nil 是配置错误,panic;不配置时用缺省客户端(整体超时 DefaultWebhookTimeout)。

func WithWebhookSigner added in v0.49.0

func WithWebhookSigner(s Signer) WebhookSinkOption

WithWebhookSigner 配置发侧签名器:Sign 的输入即发出的 body 字节—— 收端用 NewHMACValidator 系(同 secret 同选项)验签必过(配对约定见 包文档发出侧↔收侧配对表)。nil 是配置错误,panic。

Jump to

Keyboard shortcuts

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