第一章:Go语言网络代理安全加固概述
网络代理作为现代云原生架构中流量调度、身份鉴权与策略控制的关键中间件,其安全性直接关系到后端服务的可信边界。Go语言凭借其轻量协程、静态编译、内存安全机制及丰富的标准库(如 net/http、crypto/tls),成为构建高并发、低延迟代理服务的理想选择。然而,默认配置下的Go代理(如基于 httputil.NewSingleHostReverseProxy 的实现)往往暴露于未授权访问、TLS降级、请求走私、头部注入等风险之中,亟需系统性安全加固。
核心威胁面识别
常见攻击路径包括:
- 明文HTTP代理导致敏感头信息(如
Authorization、Cookie)泄露 - 缺乏Host头校验引发虚拟主机混淆或DNS重绑定攻击
- TLS配置宽松(如启用SSLv3、弱密码套件)造成中间人劫持
- 未限制请求体大小或超时时间,诱发DoS资源耗尽
安全基线配置原则
- 强制启用TLS 1.2+,禁用不安全协议与导出密码套件
- 对所有入站连接启用双向mTLS认证(客户端证书校验)
- 使用
http.Transport的DialContext和TLSClientConfig精确控制底层连接行为
示例:最小化安全代理初始化代码
proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: "backend.example.com"})
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP256},
InsecureSkipVerify: false, // 生产环境必须设为 false
},
// 拒绝HTTP/1.0,强制HTTP/1.1+ 或 HTTP/2
ForceAttemptHTTP2: true,
}
// 注:实际部署中还需结合 http.Handler 实现 Host 白名单校验与请求头清理
| 安全项 | 推荐值 | 风险说明 |
|---|---|---|
| TLS最低版本 | tls.VersionTLS12 |
防止POODLE等降级攻击 |
| 请求体最大尺寸 | < 10MB(通过 http.MaxBytesReader 包装) |
避免内存溢出 |
| 连接空闲超时 | 30s |
减少长连接被滥用的可能性 |
第二章:禁用危险HTTP Header的三步加固实践
2.1 危险Header识别原理与CVE-2023-XXXX攻击链分析
危险Header识别依赖于预定义的高风险字段模式匹配与上下文语义分析。核心逻辑在于拦截含服务端注入意图的Header,如 X-Forwarded-For 携带恶意协议头、Content-Type 伪装为 application/json; charset=utf-7 触发编码绕过。
关键检测规则示例
# 检测异常Header组合:X-Original-URL + X-Rewrite-URL + 非标准HTTP方法
dangerous_patterns = [
(r"X-Original-URL:.*https?://", "SSRF风险"),
(r"X-Rewrite-URL:.*\.\./", "路径遍历"),
(r"Content-Type:.*charset=utf-7", "编码绕过")
]
该规则集在请求解析早期触发,避免后续路由/模板引擎误判;正则中 .* 允许跨行匹配,charset=utf-7 是CVE-2023-XXXX利用IE遗留解码器的关键载荷标识。
CVE-2023-XXXX攻击链关键节点
| 阶段 | Header操作 | 利用目标 |
|---|---|---|
| 1. 绕过WAF | X-Forwarded-Proto: https + X-Forwarded-Host: attacker.com |
伪造信任上下文 |
| 2. 注入执行 | X-Callback-Url: data:text/html,<script>fetch('/admin/api')</script> |
前端JS沙箱逃逸 |
graph TD
A[客户端发送恶意Header] --> B{WAF规则匹配}
B -- 匹配失败 --> C[反向代理转发]
C --> D[Node.js Express中间件解析X-Callback-Url]
D --> E[eval-like动态加载导致CSP失效]
2.2 Go标准库net/http中Header过滤机制深度解析
Go 的 net/http 在 Header 处理中隐式执行字段级过滤,核心逻辑位于 header.go 的 isTrustedHeader 和 shouldFilterHeader 函数中。
过滤触发场景
- HTTP/2 帧头字段(如
:method,:path)被自动忽略 - 以
X-开头的非标准头在ReverseProxy中默认透传,但Transfer-Encoding、Connection等被强制移除 Host字段在客户端请求中由Request.Write自动规范化,服务端则由Server拦截校验
关键过滤逻辑示例
// src/net/http/header.go 中的典型判断
func shouldFilterHeader(key string, isReq bool) bool {
// 客户端请求中禁止设置某些敏感头
if !isReq && (key == "Transfer-Encoding" || key == "Connection") {
return true // 服务端响应中禁止返回这些头
}
return strings.HasPrefix(key, "X-") && !isTrustedHeader(key)
}
该函数在 WriteHeader 和 Write 调用链中被多次校验;isReq 参数区分请求/响应上下文,确保语义安全。
默认过滤头对照表
| Header 名称 | 请求中是否过滤 | 响应中是否过滤 | 说明 |
|---|---|---|---|
Connection |
否 | 是 | 防止连接复用污染 |
Transfer-Encoding |
否 | 是 | 由 Transport 自动管理 |
Host |
是(客户端) | 否 | 客户端由 URL.Host 提供 |
graph TD
A[HTTP Request/Response] --> B{Header Set/Write}
B --> C[shouldFilterHeader?]
C -->|true| D[Drop header silently]
C -->|false| E[Encode & transmit]
2.3 基于ReverseProxy中间件的Header白名单强制裁剪实现
在反向代理链路中,上游服务常暴露敏感请求头(如 X-Forwarded-For、Authorization),需在网关层实施最小化透传策略。
裁剪逻辑设计
仅保留预设白名单头:Host, User-Agent, Accept, Content-Type, X-Request-ID。
实现代码(Go + net/http/httputil)
func headerWhitelistMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 白名单定义(不可变)
whitelist := map[string]bool{
"Host": true,
"User-Agent": true,
"Accept": true,
"Content-Type": true,
"X-Request-ID": true,
}
// 清除非白名单Header
for name := range r.Header {
if !whitelist[strings.ToLower(name)] {
r.Header.Del(name)
}
}
next.ServeHTTP(w, r)
})
}
逻辑分析:该中间件在请求进入代理前执行,遍历原始
r.Header并按小写键匹配白名单。strings.ToLower(name)确保大小写不敏感匹配;r.Header.Del()直接移除非授权头,避免后续透传风险。
白名单头用途对照表
| Header名 | 用途说明 | 是否透传 |
|---|---|---|
Host |
路由必需 | ✅ |
Authorization |
敏感凭证,应由认证中心统一处理 | ❌ |
X-Real-IP |
可能被伪造,改用可信源提取 | ❌ |
处理流程(mermaid)
graph TD
A[Client Request] --> B{Header遍历}
B --> C[小写转换匹配白名单]
C -->|命中| D[保留]
C -->|未命中| E[删除]
D & E --> F[转发至后端服务]
2.4 自定义Transport层Header剥离与日志审计联动方案
为实现敏感字段的传输层防护与审计溯源统一,需在Transport层(如Netty ChannelHandler或Spring Cloud Gateway GlobalFilter)动态剥离特定Header(如X-Auth-Token、X-Trace-ID),并将剥离动作同步写入审计日志。
审计事件结构设计
| 字段 | 类型 | 说明 |
|---|---|---|
event_time |
ISO8601 | 剥离发生时间 |
client_ip |
string | 源IP |
stripped_headers |
list | 被移除Header键名数组 |
剥离与审计联动逻辑
// 在ChannelInboundHandler中拦截并审计
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
List<String> stripped = new ArrayList<>();
// 剥离敏感Header并记录
for (String header : SENSITIVE_HEADERS) {
if (req.headers().contains(header)) {
req.headers().remove(header); // 实际剥离
stripped.add(header);
}
}
if (!stripped.isEmpty()) {
auditLogger.info("TRANSPORT_HEADER_STRIP",
Map.of("client_ip", ctx.channel().remoteAddress().toString(),
"stripped_headers", stripped));
}
}
ctx.fireChannelRead(msg);
}
该逻辑确保Header剥离不可绕过,且审计日志携带完整上下文;auditLogger需配置异步非阻塞写入,避免影响传输性能。
数据同步机制
graph TD
A[HTTP请求入站] --> B{Transport层拦截}
B --> C[扫描并剥离敏感Header]
C --> D[生成审计事件]
D --> E[异步写入审计中心]
D --> F[透传至业务层]
2.5 生产环境Header加固效果验证与自动化测试用例设计
验证核心策略
采用“请求拦截 + 响应断言”双路验证模型,覆盖 CSP、X-Content-Type-Options、Strict-Transport-Security 等 7 类关键 Header。
自动化测试用例设计
- 使用 Playwright 启动真实浏览器上下文,模拟多终端(移动端/桌面端)访问
- 每次请求注入自定义 UA 和 referer,触发服务端差异化 Header 注入逻辑
- 断言严格校验 Header 存在性、值格式及缺失项(如
X-Powered-By必须不存在)
关键验证代码示例
// playwright.test.ts:Header 安全性断言
await expect(page).toHaveHTTPResponse((resp) => {
const headers = resp.headers();
return (
headers['content-security-policy']?.includes("default-src 'self'") &&
headers['x-content-type-options'] === 'nosniff' &&
!headers['x-powered-by'] // 显式拒绝泄露技术栈
);
});
逻辑说明:
toHaveHTTPResponse捕获原始响应(非渲染后),确保验证发生在网络层;includes("default-src 'self'")防止 CSP 被弱策略绕过;!headers['x-powered-by']为硬性黑名单检查,避免框架指纹暴露。
测试覆盖率矩阵
| Header 类型 | 必检项 | 验证方式 |
|---|---|---|
| CSP | default-src, script-src |
正则匹配策略字段 |
| HSTS | max-age=31536000 |
精确字符串比对 |
| Referrer-Policy | strict-origin-when-cross-origin |
全值校验 |
graph TD
A[发起HTTPS请求] --> B{Nginx/CDN注入}
B --> C[应用层二次校验]
C --> D[Playwright捕获响应]
D --> E[结构化解析Headers]
E --> F[执行断言规则集]
F --> G[生成合规报告]
第三章:四层协议校验体系构建
3.1 TCP/UDP连接层合法性校验:源IP信誉与连接速率控制
连接层校验是抵御SYN洪泛、扫描探测等L4层攻击的第一道防线,核心依赖源IP行为画像与实时速率决策。
源IP信誉动态评估模型
基于滑动窗口(5分钟)统计以下维度:
- ✅ 连接成功率(>95% → 信任分+1)
- ❌ 异常重传率(>30% → 信誉降级)
- ⚠️ 多端口扫描行为(3秒内≥5个不同目标端口 → 置入灰名单)
实时速率控制策略
# 基于令牌桶的每IP限速(单位:连接/秒)
rate_limit = max(1, int(100 / (1 + math.log10(ip_reputation_score + 1))))
# ip_reputation_score ∈ [0, 100];log平滑避免极端惩罚
逻辑分析:信誉越低,基础配额越小;log10确保从0→10分仅压缩2倍配额,避免误杀正常爬虫。
| 信誉分 | 允许速率(conn/s) | 行为标签 |
|---|---|---|
| 90–100 | 50 | 白名单 |
| 60–89 | 10 | 正常用户 |
| 0–59 | 1–3 | 观察/限频 |
决策流程
graph TD
A[新连接请求] --> B{IP是否在黑名单?}
B -->|是| C[拒绝并记录]
B -->|否| D[查信誉分 & 当前令牌]
D --> E{令牌充足?}
E -->|是| F[放行并消耗令牌]
E -->|否| G[返回TCP RST或UDP ICMP Port Unreachable]
3.2 TLS握手阶段SNI、ALPN及ClientHello特征指纹校验
TLS 1.2/1.3 握手初期,ClientHello 消息承载关键扩展字段,成为流量识别与策略路由的核心依据。
SNI:虚拟主机路由基石
客户端在 ClientHello.extensions 中携带 server_name 扩展,明文指定目标域名(如 example.com),不加密但不可省略(对多租户HTTPS服务至关重要)。
ALPN:协议协商前置通道
ALPN 扩展声明支持的应用层协议列表(如 h2, http/1.1),网关据此分发至对应后端集群:
# 解析ClientHello中ALPN列表的典型逻辑(伪代码)
alpn_ext = find_extension(client_hello, 16) # type=16 for ALPN
proto_len = int.from_bytes(alpn_ext[2:4], 'big')
protocols = []
offset = 4
while offset < 4 + proto_len:
proto_len_byte = alpn_ext[offset]
proto = alpn_ext[offset+1:offset+1+proto_len_byte].decode()
protocols.append(proto)
offset += 1 + proto_len_byte
# → ['h2', 'http/1.1']
该解析需严格遵循 RFC 7301:每个协议名前缀1字节长度字段,整体长度字段为网络字节序。
ClientHello指纹维度
| 特征项 | 提取位置 | 典型用途 |
|---|---|---|
| Random | client_random (32B) |
TLS版本推断 |
| Cipher Suites | cipher_suites |
客户端能力画像 |
| Extensions | 扩展类型与顺序 | 设备/库指纹识别 |
graph TD
A[ClientHello] --> B[SNI解析]
A --> C[ALPN提取]
A --> D[Extension顺序归一化]
B & C & D --> E[指纹哈希生成]
E --> F[匹配规则引擎]
3.3 HTTP/1.1与HTTP/2协议语义级解析与非法帧拦截
HTTP/1.1 基于文本行的请求-响应模型,而 HTTP/2 引入二进制帧层,支持多路复用与头部压缩。语义一致性是协议升级的核心约束:GET /api/v1/users 在两者中语义等价,但传输形态迥异。
帧结构校验关键点
- 所有 HTTP/2 帧必须以 9 字节头部起始(Length:3 + Type:1 + Flags:1 + StreamID:4)
SETTINGS帧不可出现在流半关闭后RST_STREAM帧需携带合法错误码(如0x08表示 CANCEL)
非法帧拦截逻辑示例
def validate_http2_frame(header_bytes):
if len(header_bytes) < 9:
raise ValueError("Frame header too short") # 必须9字节
length = int.from_bytes(header_bytes[0:3], 'big')
frame_type = header_bytes[3]
if frame_type not in {0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0x8}: # 仅允许标准帧类型
raise ProtocolError("Unknown frame type")
return length, frame_type
该函数在 TLS 解密后、应用层分发前执行——确保非法帧被阻断于语义解析入口,避免状态污染。
| 检查项 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 协议标识 | HTTP/1.1 字符串 |
PRI * HTTP/2.0\r\n |
| 请求行解析 | 空格分隔三元组 | HPACK 解压 + 帧重组 |
| 非法字符拦截点 | CRLF 后续空格/Tab |
帧头长度溢出/类型越界 |
graph TD
A[TLS Record] --> B{Frame Header}
B -->|Valid| C[HPACK Decode]
B -->|Invalid| D[Reject & Log]
C --> E[Semantic Validation]
E -->|OK| F[Route to Handler]
E -->|Malformed| D
第四章:证书钉扎(Certificate Pinning)强制启用机制
4.1 X.509证书链验证漏洞与钉扎策略选型对比(SPKI vs SubjectPublicKeyInfo)
证书链验证的常见漏洞根源
中间CA私钥泄露或错误签发可导致合法但恶意的证书被信任。传统链式验证缺乏对终端公钥的强绑定,攻击者可利用短生命周期子CA绕过吊销检查。
SPKI钉扎:更精简、更鲁棒
# 计算SPKI指纹(RFC 7469)
import hashlib, base64
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.primitives import serialization
cert = load_pem_x509_certificate(pem_data)
spki = cert.public_key().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
pin = base64.b64encode(hashlib.sha256(spki).digest()).decode()
# 输出示例:Wec6qFjC+VUaT8HbNtBZKJQmGvXyDfLrMnOpQsRtUvWxYzA=
该方式仅哈希公钥本身(含算法标识),不依赖证书主体或签名,抗颁发者篡改;但需确保密钥长期稳定。
SubjectPublicKeyInfo vs SPKI语义差异
| 维度 | SubjectPublicKeyInfo | SPKI Pin |
|---|---|---|
| 内容范围 | ASN.1结构体(含算法OID+参数+key) | 其DER编码的哈希值 |
| 抗签发者欺骗 | ✅(不依赖issuer字段) | ✅ |
| 算法迁移兼容性 | ❌(更换EC曲线即失效) | ❌ |
验证流程关键路径
graph TD
A[客户端收到证书链] --> B{执行标准链验证}
B -->|通过| C[提取终端证书SPKI]
B -->|失败| D[拒绝连接]
C --> E[比对预置SPKI指纹]
E -->|匹配| F[建立TLS连接]
E -->|不匹配| G[触发钉扎失败告警]
4.2 Go crypto/tls中自定义VerifyPeerCertificate钩子的高可靠实现
自定义 VerifyPeerCertificate 是 TLS 客户端身份验证的关键扩展点,需兼顾安全性与健壮性。
核心约束条件
- 必须在证书链验证完成前执行(即早于系统默认校验)
- 不得阻塞或 panic,否则导致连接中断
- 需显式调用
x509.VerifyOptions.Roots或提供可信锚点
推荐实现模式
cfg := &tls.Config{
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return errors.New("no certificate presented")
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return fmt.Errorf("parse cert failed: %w", err)
}
// 自定义域名/策略校验(如 SPIFFE ID、OID extension)
if !isValidIdentity(cert) {
return errors.New("unauthorized identity")
}
return nil // 允许系统继续执行标准链验证
},
}
该钩子仅做附加校验,不替代系统链验证;
verifiedChains为空时仍可安全访问rawCerts,但不应跳过x509.ParseCertificate的错误检查。
常见陷阱对比
| 场景 | 风险 | 推荐做法 |
|---|---|---|
直接返回 nil 跳过所有校验 |
完全绕过 PKI 安全保障 | 仅添加校验逻辑,保持链验证通路 |
在钩子中调用 cert.Verify() |
可能重复验证或资源泄漏 | 依赖 verifiedChains 或委托给标准流程 |
graph TD
A[Client Handshake] --> B[收到 server cert]
B --> C[调用 VerifyPeerCertificate]
C --> D{返回 error?}
D -->|Yes| E[Abort connection]
D -->|No| F[继续系统链验证]
F --> G[最终验证通过/失败]
4.3 动态证书钉扎更新机制:基于etcd的PinSet热加载与版本回滚
传统静态钉扎在证书轮换时需重启服务,而本机制通过 etcd 实现 PinSet 的实时感知与原子切换。
数据同步机制
监听 etcd /pins/v2/ 下的 revision 变更,触发本地 PinSet 内存快照替换:
// Watch etcd key prefix and apply new PinSet atomically
watchChan := client.Watch(ctx, "/pins/v2/", client.WithPrefix())
for resp := range watchChan {
for _, ev := range resp.Events {
if ev.IsCreate() || ev.IsModify() {
pins, _ := parsePinSet(ev.Kv.Value) // 解析 PEM+SHA256 组合
atomic.StorePointer(¤tPins, unsafe.Pointer(&pins))
}
}
}
parsePinSet 提取公钥指纹并校验签名有效性;atomic.StorePointer 保证零停机切换,避免 TLS 握手期间状态不一致。
版本回滚能力
etcd 保留最近 3 个历史版本(revision + TTL),支持按 pinset_version=20240512-1 回退。
| 版本标识 | 生效时间 | 签名者 | 回滚窗口 |
|---|---|---|---|
20240512-0 |
2024-05-12T08:00Z | ca-ops | 72h |
20240512-1 |
2024-05-12T14:30Z | sec-team | 48h |
流程编排
graph TD
A[etcd Watch] --> B{Key变更?}
B -->|是| C[解析新PinSet]
B -->|否| D[空闲等待]
C --> E[校验签名+指纹]
E -->|有效| F[原子更新内存引用]
E -->|无效| G[告警并保留旧版]
4.4 钉扎失败时的分级降级策略与安全告警通道集成(Prometheus + Alertmanager)
当证书钉扎校验失败时,系统需避免直接中断服务,而应按风险等级执行差异化响应。
降级策略分级设计
- L1(低风险):仅记录审计日志,维持连接(如内部测试域名)
- L2(中风险):禁用非关键功能(如用户头像加载),保留核心交易
- L3(高风险):强制断连并触发安全告警(如公网API端点)
Prometheus 告警规则示例
# alert_rules.yml
- alert: PinningFailureCritical
expr: sum by (host, cert_fingerprint) (pinning_failure_total{severity="critical"}[5m]) > 0
for: 30s
labels:
severity: critical
channel: "security-pagerduty"
annotations:
summary: "Critical pinning failure on {{ $labels.host }}"
该规则每30秒检查过去5分钟内高危钉扎失败计数;severity="critical"标签驱动Alertmanager路由至安全通道;channel标签用于多通道分发。
Alertmanager 路由配置
| 接收器 | 触发条件 | 响应动作 |
|---|---|---|
webhook-audit |
severity=~"low|medium" |
写入SIEM并发送企业微信通知 |
pagerduty-security |
severity="critical" |
触发Paging并冻结对应API密钥 |
安全响应流程
graph TD
A[钉扎失败事件] --> B{Severity判定}
B -->|low| C[审计日志+企业微信]
B -->|medium| D[功能降级+SIEM写入]
B -->|critical| E[断连+PagerDuty+密钥冻结]
第五章:附录与参考实现代码仓库说明
代码仓库组织结构
本项目托管于 GitHub,主仓库地址为 https://github.com/infra-arch/observability-stack(公开可克隆)。仓库采用模块化布局:/core 目录包含 Prometheus + Grafana 的 Helm Chart 定义;/examples/k8s-aws 提供 EKS 环境下开箱即用的监控部署模板;/scripts 中存放经 CI 验证的 Bash 自动化脚本(如 deploy-alert-rules.sh 支持按标签批量注入 Alertmanager 规则);/docs/reference 下存有所有 YAML 文件的 Schema 校验文件(JSON Schema 格式),确保配置符合 v2.45+ Prometheus 语义约束。
版本兼容性矩阵
| 组件 | 支持版本范围 | 已验证环境 | 关键限制 |
|---|---|---|---|
| Prometheus | v2.37–v2.49 | Kubernetes v1.24+ | v2.48+ 需启用 --web.enable-admin-api 才支持热重载 |
| Grafana | v9.5.14–v10.4.2 | Docker Desktop 4.25 | 插件 grafana-worldmap-panel 仅兼容 v10.2.x 及以下 |
| Alertmanager | v0.25.0–v0.26.0 | AWS EC2 (Ubuntu 22.04) | v0.26.0 要求 systemd v250+ |
实战调试工具链
仓库内置 make debug-env 命令,执行后自动启动本地 Minikube 集群并部署完整栈(含模拟业务 Pod、自定义 exporter 及预置告警规则)。该流程会生成 /tmp/debug-report.json,包含实时采集指标覆盖率(如 rate(http_requests_total[5m]) 是否被正确抓取)、Grafana 数据源连通性诊断结果及 Alertmanager 静默组匹配日志片段。开发者可通过 kubectl logs -n monitoring deploy/prometheus-server -c config-reloader 直接查看配置热加载失败的原始错误堆栈(例如因 duration 字段缺失导致 rule file 解析中断)。
# 示例:快速验证自定义 exporter 集成
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.instance=="demo-exporter:9100") | .health'
# 输出 "up" 表示目标健康且指标可采集
文档与测试资产
/test/integration 目录下包含基于 pytest 的端到端测试套件,覆盖 17 个核心场景(如“当 CPU 使用率持续超阈值 5 分钟时触发 PagerDuty Webhook”)。每个测试用例均绑定独立命名空间与资源清理钩子,避免污染测试环境。CI 流水线(GitHub Actions)强制要求所有 PR 必须通过 test-alert-firing 用例——该用例会动态注入伪造高负载指标,并断言 Alertmanager 的 /api/v2/alerts 接口在 90 秒内返回至少 1 条 active 状态告警。
flowchart LR
A[PR 提交] --> B[CI 启动 minikube]
B --> C[部署 observability-stack]
C --> D[运行 test-alert-firing.py]
D --> E{告警是否在 90s 内激活?}
E -->|是| F[标记 CI 通过]
E -->|否| G[输出 Prometheus 查询日志与 target 状态快照]
社区贡献指引
所有 Issue 模板均预置 environment-checklist 字段(如要求提供 kubectl get pods -n monitoring -o wide 输出),大幅缩短问题复现周期。仓库根目录的 CONTRIBUTING.md 明确规定:新增 exporter 配置必须同步更新 /docs/reference/exporters.yaml 的 JSON Schema,并通过 jsonschema -i exporters.yaml exporters.schema.json 验证。最近一次合并(commit a7f3e1d)修复了 Windows 用户无法运行 ./scripts/setup.sh 的路径分隔符问题,补丁已通过 WSL2 与 Git Bash 双环境验证。
