第一章:Go语言在金融级环境中的定位与选型原则
在高并发、低延迟、强一致性的金融级系统中,Go语言凭借其原生协程(goroutine)、快速启动时间、确定性GC(自Go 1.14起STW通常低于250μs)、静态链接可执行文件及内存安全边界等特性,已成为清算引擎、实时风控网关、高频行情分发服务及分布式账本中间件的主流实现语言之一。
核心定位特征
- 确定性性能表现:相比JVM类运行时,Go避免了复杂GC调优与类加载不确定性,在毫秒级SLA场景下更易达成P99
- 运维友好性:单二进制部署消除了依赖地狱,
go build -ldflags="-s -w"可生成约8MB无调试符号的静态可执行文件,适配容器化金融私有云环境; - 生态可控性:标准库内置
net/http/httputil、crypto/tls、encoding/json等生产就绪组件,第三方依赖(如gRPC-Go)经CNCF认证,满足金融行业对供应链审计的强要求。
关键选型原则
- 拒绝过度抽象:禁用反射密集型框架(如泛型ORM),优先选用
sqlc生成类型安全SQL绑定,保障编译期错误发现; - 显式错误处理:强制
if err != nil分支,禁止panic跨服务边界传播,风控核心路径须配置defer func() { if r := recover(); r != nil { log.Panic(r) } }()兜底; - 可观测性前置:所有HTTP服务默认集成
prometheus/client_golang,通过以下代码注入指标:
// 初始化全局计数器(示例:交易请求总量)
var requestTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "financial_api_requests_total",
Help: "Total number of API requests processed",
},
[]string{"endpoint", "status_code"},
)
func init() {
prometheus.MustRegister(requestTotal) // 注册至默认注册表
}
兼容性验证清单
| 检查项 | 金融场景要求 | Go实现方式 |
|---|---|---|
| TLS 1.3支持 | 监管合规必需 | crypto/tls 默认启用(Go 1.12+) |
| 纳秒级时间精度 | 订单撮合时序一致性 | time.Now().UnixNano() |
| 内存分配可预测性 | 防止GC抖动影响订单延迟 | 使用sync.Pool复用结构体对象 |
第二章:RHEL 9+系统级Go开发环境构建
2.1 Go二进制分发版校验与FIPS兼容性编译实践
Go官方二进制分发版需通过SHA256与GPG双重校验确保完整性与来源可信:
# 下载并校验
curl -O https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
curl -O https://go.dev/dl/go1.22.5.linux-amd64.tar.gz.sha256sum
curl -O https://go.dev/dl/go1.22.5.linux-amd64.tar.gz.asc
sha256sum -c go1.22.5.linux-amd64.tar.gz.sha256sum # 验证哈希一致性
gpg --verify go1.22.5.linux-amd64.tar.gz.asc # 验证Go团队签名
sha256sum -c读取校验文件逐行比对;gpg --verify要求已导入Go官方公钥(gpg --recv-keys 77D0E99A32F2C60B)。
FIPS合规需禁用非FIPS算法,须启用-tags=netgo,osusergo并链接OpenSSL FIPS模块:
| 编译标志 | 作用 |
|---|---|
-tags=fips |
启用FIPS模式(需补丁版Go或Go 1.23+) |
-ldflags="-extldflags '-Wl,--rpath,/usr/local/ssl/fips/lib'" |
强制动态链接FIPS库 |
graph TD
A[下载官方tar.gz] --> B[SHA256校验]
B --> C[GPG签名验证]
C --> D[FIPS环境准备]
D --> E[源码级编译或静态链接]
2.2 多版本Go管理(gvm替代方案)与企业级PATH隔离策略
现代企业CI/CD流水线需严格隔离Go版本,避免go build因GOROOT污染导致构建不一致。推荐采用asdf统一管理Go多版本,兼顾轻量性与可审计性。
安装与初始化
# 安装 asdf(支持插件化语言管理)
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
. ~/.asdf/asdf.sh
asdf plugin-add golang https://github.com/kennyp/asdf-golang.git
该命令拉取稳定版asdf并注册官方维护的Go插件,--branch确保环境可复现;. ~/.asdf/asdf.sh临时注入shell环境变量。
企业级PATH隔离策略
| 环境类型 | GOROOT路径 | PATH前缀 | 隔离机制 |
|---|---|---|---|
| 开发环境 | ~/.asdf/installs/golang/1.21.6 |
~/.asdf/shims |
shims动态代理 |
| 构建容器 | /opt/go/1.20.14 |
/opt/go/1.20.14/bin |
构建时显式覆盖PATH |
graph TD
A[开发者执行 go version] --> B[调用 ~/.asdf/shims/go]
B --> C{检测当前目录 .tool-versions}
C -->|go 1.21.6| D[路由至 ~/.asdf/installs/golang/1.21.6/bin/go]
C -->|go 1.20.14| E[路由至 ~/.asdf/installs/golang/1.20.14/bin/go]
关键在于.tool-versions文件声明版本,shims层实现零侵入切换,彻底规避gvm的source污染风险。
2.3 CGO_ENABLED=0与静态链接加固:规避动态库供应链风险
Go 默认启用 CGO,导致二进制依赖系统 glibc 等动态库,引入不可控的第三方共享库风险。禁用 CGO 可实现纯静态链接,彻底剥离运行时外部依赖。
静态构建命令
CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o app-static .
CGO_ENABLED=0:禁用 C 语言互操作,强制使用 Go 自研 syscall 封装;-a:强制重新编译所有依赖包(含标准库);-ldflags '-extldflags "-static"':要求底层链接器生成完全静态可执行文件。
动态 vs 静态对比
| 特性 | 动态链接(默认) | 静态链接(CGO_ENABLED=0) |
|---|---|---|
| 依赖体积 | 小(~10MB) | 大(~20MB) |
| 运行环境要求 | 需匹配 glibc 版本 | 无系统库依赖 |
| 供应链攻击面 | 高(glibc、libpthread 等) | 极低(仅 Go 运行时) |
安全加固流程
graph TD
A[源码] --> B[CGO_ENABLED=0]
B --> C[Go syscall 替代 cgo]
C --> D[静态链接 net/http、os/exec 等]
D --> E[单二进制交付]
2.4 内核级资源限制(cgroups v2 + systemd scope)对Go runtime的约束配置
Go runtime 的调度器与内存分配器高度依赖底层可用资源,而 cgroups v2 通过统一层级(unified hierarchy)为进程提供精确的 CPU、内存、IO 等硬性边界。systemd scope 单元则将 Go 应用进程自动纳入 /sys/fs/cgroup/ 对应路径,实现声明式资源绑定。
cgroups v2 内存限制生效机制
当 MemoryMax=512M 应用于 scope 时,Go 的 runtime.MemStats.Sys 不再反映宿主机总量,而是受 memory.current 和 memory.max 双重钳制——触发 OOM Killer 前,runtime.GC() 会更激进地回收,且 mmap 分配失败将直接返回 ENOMEM。
Go 启动时适配建议
# 创建受限 scope 并运行 Go 程序
systemd-run \
--scope \
--property=MemoryMax=512M \
--property=CPUQuota=50% \
./myapp
此命令创建 transient scope,
MemoryMax强制限制 RSS+PageCache 总和;CPUQuota=50%将cpu.max设为50000 100000,使 Go scheduler 在GOMAXPROCS自动裁剪后仍受限于 cgroup 的cpu.weight(默认 100)与cpu.max联合调控。
| 参数 | cgroups v2 路径 | Go runtime 影响 |
|---|---|---|
MemoryMax |
/sys/fs/cgroup/myapp/memory.max |
触发 runtime.SetMemoryLimit() 隐式对齐,抑制 heap growth |
CPUQuota |
/sys/fs/cgroup/myapp/cpu.max |
限制 sched_yield 有效性,降低 P 复用率 |
// 启动时主动读取 cgroup 约束,优化 GC 阈值
if limit, err := readCgroupMemLimit(); err == nil {
debug.SetMemoryLimit(limit * 9 / 10) // 预留 10% buffer
}
readCgroupMemLimit()从/sys/fs/cgroup/memory.max解析字节值(支持"max"字符串表示无界);debug.SetMemoryLimit()自 Go 1.19 起启用,使 GC 在达到阈值前主动触发,避免被内核 OOM 终止。
graph TD A[Go 进程启动] –> B{systemd 创建 scope} B –> C[cgroups v2 控制组挂载] C –> D[CPU/Memory/IO 参数写入] D –> E[Go runtime 初始化] E –> F[自动检测 cgroup limits] F –> G[调整 GOMAXPROCS / SetMemoryLimit]
2.5 SELinux策略定制:为Go服务进程定义最小权限域(type enforcement)
为何需要专用域?
Go二进制通常静态链接、无依赖库,传统 httpd_t 或 unconfined_t 过度放权,违背最小权限原则。
定义服务类型与域
# go_service.te
type go_service_t;
type go_service_exec_t;
init_daemon_domain(go_service_t, go_service_exec_t)
allow go_service_t self:process { transition sigchld };
allow go_service_t self:tcp_socket { connect listen };
go_service_t是运行时域,go_service_exec_t是可执行文件类型;init_daemon_domain()自动生成基础进程上下文切换与信号处理规则;- 显式限定仅允许 TCP 连接与监听,禁用
execmem、write等高危权限。
权限对比表
| 权限项 | unconfined_t |
go_service_t |
|---|---|---|
execmem |
✅ | ❌ |
write (tmpfs) |
✅ | ❌ |
connect (tcp) |
✅ | ✅(显式授权) |
策略加载流程
graph TD
A[编写 .te/.if/.fc] --> B[checkmodule -M -m]
B --> C[semodule_package -o go_service.pp]
C --> D[semodule -i go_service.pp]
第三章:金融合规驱动的安全加固实践
3.1 TLS 1.3强制启用与国密SM2/SM4算法集成(基于go-tls-sm)
go-tls-sm 是专为国产密码合规设计的 Go 原生 TLS 扩展库,深度适配 TLS 1.3 协议栈,禁用所有不安全降级路径,强制启用 1-RTT 握手与 PSK 恢复机制。
核心配置示例
config := &tls.Config{
MinVersion: tls.VersionTLS13,
CurvePreferences: []tls.CurveID{sm2.CurveP256Sm2}, // 强制SM2椭圆曲线
CipherSuites: []uint16{tls.TLS_SM2_WITH_SM4_GCM_SHA256},
VerifyPeerCertificate: sm2.VerifyPeerCertificate, // 国密证书链校验
}
该配置禁用 TLS 1.2 及以下版本,仅允许 TLS_SM2_WITH_SM4_GCM_SHA256 密码套件(RFC 8998 扩展),CurveP256Sm2 确保密钥交换使用 SM2 标准参数,VerifyPeerCertificate 替换默认 X.509 验证逻辑,支持 SM2 签名与 GB/T 38636-2020 证书格式。
支持的国密密码套件对照表
| 密码套件名称 | 密钥交换 | 对称加密 | 认证机制 | 标准依据 |
|---|---|---|---|---|
TLS_SM2_WITH_SM4_GCM_SHA256 |
SM2 | SM4-GCM | SM2-SHA256 | GM/T 0024-2014 |
TLS_SM2_WITH_SM4_CCM_SHA256 |
SM2 | SM4-CCM | SM2-SHA256 | GM/T 0024-2014 |
协议协商流程(TLS 1.3 + 国密)
graph TD
A[ClientHello] -->|KeyShare: sm2_p256| B[ServerHello]
B --> C[EncryptedExtensions + Certificate<br>SM2签名+SM4加密]
C --> D[Finished<br>SM3-HMAC验证]
3.2 敏感配置项零明文化:Vault Agent Sidecar与Go config.Provider统一接入
在微服务架构中,敏感配置(如数据库密码、API密钥)严禁硬编码或明文挂载。Vault Agent Sidecar 以“注入式”方式将动态凭证安全注入容器内存文件系统,配合 Go 生态 config.Provider 接口实现透明适配。
统一接入层设计
- 实现
vault.Provider结构体,嵌入config.Provider - 重写
Get(key string) (string, error)方法,自动前缀补全/secrets/data/ - 支持 TTL 自动续期与失效回调
配置加载示例
// 初始化 Vault Provider(自动连接 agent)
provider := vault.NewProvider(vault.WithAddress("http://127.0.0.1:8200"))
cfg := config.Load(config.WithProvider(provider))
dbPass, _ := cfg.Get("database.password") // 实际读取 /run/secrets/database.password
逻辑分析:
vault.NewProvider默认通过 Unix socket(/var/run/vault-agent/socket:8200)与 Sidecar 通信;Get()内部触发 Vault Agent 的renderAPI,返回解密后值;WithAddress仅用于 fallback 场景,生产环境应省略以强制走本地 socket。
凭证生命周期对比
| 方式 | 加载时机 | 自动续期 | 文件挂载 | 内存驻留 |
|---|---|---|---|---|
| Vault Agent file | 启动时 | ✅ | ✅ | ❌ |
| Vault Agent Caching | 运行时 | ✅ | ❌ | ✅ |
graph TD
A[App Pod] --> B[Vault Agent Sidecar]
B --> C{Vault Server}
C -->|TLS mTLS| D[Lease ID + TTL]
B -->|in-memory| E[App via HTTP/Socket]
3.3 内存安全增强:GODEBUG=madvdontneed=1与runtime.LockOSThread审计验证
Go 运行时默认在内存归还 OS 前调用 MADV_DONTNEED(Linux)触发页表清零,但该行为可能暴露敏感数据残留。启用 GODEBUG=madvdontneed=1 可禁用该优化,强制保留物理页内容直至真正释放。
关键行为对比
| 调试标志 | 内存归还方式 | 安全风险 |
|---|---|---|
| 默认(未设置) | MADV_DONTNEED |
页内容被清零前可能被重映射读取 |
GODEBUG=madvdontneed=1 |
MADV_FREE(Linux) |
延迟清零,配合 runtime.LockOSThread 可控规避竞态 |
配合 LockOSThread 的安全实践
func secureWorker() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
buf := make([]byte, 4096)
// 敏感数据填充(如密钥)
cryptoutil.FillRandom(buf)
// ... 使用后显式擦除
cryptoutil.Zero(buf) // 必须手动清零
}
逻辑分析:
LockOSThread确保 goroutine 绑定到固定 OS 线程,避免调度迁移导致敏感缓冲区被其他 goroutine 复用;madvdontneed=1则防止运行时自动清零干扰擦除时机。二者协同构建确定性内存生命周期。
审计验证路径
graph TD
A[启动时设置 GODEBUG=madvdontneed=1] --> B[分配 runtime.mspan]
B --> C[归还内存时跳过 MADV_DONTNEED]
C --> D[调用 runtime.sysFree → mmap with MADV_FREE]
D --> E[最终由 kernel 异步回收并清零]
第四章:全链路审计日志体系设计与落地
4.1 结构化审计日志标准(RFC 5424 + ISO/IEC 27001 Annex A.16)映射到Go zap.Logger
RFC 5424 定义了结构化 syslog 消息格式,要求包含 PRI、VERSION、TIMESTAMP、HOSTNAME、APP-NAME、PROCID、MSGID 和 STRUCTURED-DATA;ISO/IEC 27001 A.16 则强调日志的完整性、可追溯性与安全事件关联性。Zap 默认不满足二者,需定制。
关键字段映射策略
TIMESTAMP→zap.Time("time")(RFC 5424 格式:2024-03-15T13:45:30.123Z)APP-NAME/PROCID→zap.String("app", "authsvc"),zap.String("pid", "12345")STRUCTURED-DATA→zap.Object("sd", map[string]string{"eventID": "AUTH-001", "sessionID": "s-7f8a"})
自定义 Encoder 示例
type RFC5424Encoder struct {
zapcore.Encoder
}
func (e *RFC5424Encoder) AddString(key, val string) {
if key == "time" {
e.Encoder.AddString(key, time.Now().UTC().Format("2006-01-02T15:04:05.000Z"))
} else {
e.Encoder.AddString(key, val)
}
}
该封装强制时间字段符合 RFC 5424 UTC 微秒精度格式,并保留 Zap 高性能编码器链;AddString 拦截确保关键字段语义对齐 ISO 27001 A.16 的可审计性要求。
| RFC 5424 字段 | Zap 映射方式 | 合规要点 |
|---|---|---|
| VERSION | 固定写入 1 |
必须为整数 1 |
| STRUCTURED-DATA | zap.Any("sd", sdMap) |
支持嵌套结构化元数据 |
| MSGID | zap.String("msgid", …) |
建议与事件类型强绑定 |
graph TD
A[Log Entry] --> B{Zap Core}
B --> C[RFC5424Encoder]
C --> D[ISO 27001 A.16 Check]
D --> E[Write to Syslog/Secure Store]
4.2 系统调用级审计(auditd规则集)与Go应用层审计事件的时序对齐方案
为弥合内核审计日志与应用层事件的时间语义鸿沟,需建立纳秒级时序锚点。
数据同步机制
采用 CLOCK_MONOTONIC_RAW 作为跨层统一时钟源,在 auditd 规则中注入时间戳标记,在 Go 应用中通过 runtime.nanotime() 获取对齐基准:
// Go 应用层生成带锚点的审计事件
event := AuditEvent{
ID: uuid.New().String(),
Timestamp: uint64(time.Now().UnixNano()), // 与 auditd 的 audit_log_time_sync 对齐
Context: "user_login",
}
此处
UnixNano()避免系统时钟跳变影响;实际部署中需校准 Go 运行时与内核audit_log_time_sync的初始偏移(通常
关键对齐参数对照表
| 维度 | auditd 规则侧 | Go 应用侧 |
|---|---|---|
| 时间源 | CLOCK_MONOTONIC_RAW |
runtime.nanotime() |
| 精度 | ~15ns(x86_64 TSC) | ~1–10ns(取决于CPU) |
| 持久化字段 | msg=audit(... time=171...) |
Timestamp uint64 |
事件关联流程
graph TD
A[auditd 拦截 openat syscall] --> B[写入 raw timestamp + event]
C[Go 应用触发 login] --> D[调用 nanotime() 生成锚点]
B --> E[ELK/Fluentd 聚合]
D --> E
E --> F[按 ±100μs 窗口关联事件]
4.3 日志加密传输(TLS双向认证+gRPC流式日志推送)与不可抵赖性签名(Ed25519)
安全通道建立:mTLS双向认证
客户端与日志收集服务端均需持有由私有CA签发的证书,验证彼此身份。gRPC Channel 初始化时强制启用 TransportCredentials:
creds, _ := credentials.NewClientTLSFromCert(caCertPool, "log-collector.example.com")
conn, _ := grpc.Dial("log-collector.example.com:443",
grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(&mtlsAuth{clientCert: clientCert}))
caCertPool确保仅信任内网CA;clientCert用于服务端反向校验客户端身份;WithPerRPCCredentials注入证书链元数据,实现双向绑定。
日志流式签名与推送
每条日志在发送前使用本地Ed25519密钥签名,保证来源不可抵赖:
| 字段 | 类型 | 说明 |
|---|---|---|
timestamp |
int64 | 纳秒级本地生成时间(防重放) |
payload |
bytes | 原始日志JSON序列化内容 |
signature |
[64]byte | Ed25519签名(payload + timestamp) |
sig := ed25519.Sign(privateKey, append([]byte(tsStr), payload...))
logEntry := &pb.LogEntry{
Timestamp: ts,
Payload: payload,
Signature: sig,
}
stream.Send(logEntry)
append([]byte(tsStr), payload...)构造唯一签名原文,杜绝日志篡改或时间漂移导致的签名失效;ed25519.Sign使用恒定时间算法,抗侧信道攻击。
数据同步机制
graph TD
A[Agent采集日志] --> B[Ed25519签名]
B --> C[mTLS gRPC Stream]
C --> D[服务端证书校验]
D --> E[签名验签+时间戳检查]
E --> F[写入WAL+异步落盘]
4.4 审计日志归档策略:基于RHEL 9+ journal-remote的WORM存储对接实践
为满足等保2.0与GDPR对日志不可篡改性(WORM)的合规要求,RHEL 9+ 原生 journal-remote 服务可与对象存储(如MinIO启用S3 WORM bucket)深度集成。
数据同步机制
journal-remote 通过 systemd-journal-remote 以HTTP POST方式将结构化日志流式推送至远程接收端,并支持TLS双向认证与日志签名验证:
# /etc/systemd/journal-remote.conf
[Remote]
# 启用日志完整性校验(SHA256+X.509签名)
VerifyPeer=yes
TrustCertificate=/etc/pki/tls/certs/worm-ca.crt
URL=https://worm-logs.example.com:19531/upload
此配置强制启用证书链校验与端到端加密传输;
VerifyPeer确保仅接受由可信CA签发的服务端证书,防止中间人劫持;/upload路径需由后端WORM网关实现写入即锁定语义。
WORM网关适配要点
| 组件 | 要求 |
|---|---|
| 存储后端 | S3兼容、支持Object Lock(Governance/Compliance模式) |
| 接收服务 | 拦截PUT请求,调用PutObjectRetention API强制设为ComplianceMode |
| 时间锚定 | 使用硬件可信时间源(如NTP + PTP)同步所有节点系统时钟 |
graph TD
A[journal-remote client] -->|TLS 1.3 + Client Cert| B[WORM Gateway]
B --> C{Validate & Sign}
C --> D[MinIO S3 Bucket<br>Object Lock Enabled]
D --> E[Immutable Log Object<br>Retention until 2030-12-31]
第五章:企业级Go开发环境演进路线图
从单机GOPATH到模块化多团队协同
早期某金融支付中台采用统一 GOPATH + SVN 的模式,导致依赖冲突频发。2019年迁移至 Go Modules 后,通过 go mod vendor 锁定三方库 SHA256,并结合 Git Submodule 管理内部私有 SDK(如 gitlab.internal/payment-sdk),使跨业务线复用率提升 63%。所有服务均启用 -mod=readonly 构建约束,CI 流水线强制校验 go.sum 签名一致性。
统一构建与镜像标准化体系
企业级构建不再依赖本地 go build,而是基于 BuildKit 构建的多阶段 Dockerfile:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o /bin/app ./cmd/server
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /bin/app /usr/local/bin/app
EXPOSE 8080
CMD ["/usr/local/bin/app"]
所有镜像统一继承自内部 registry.internal/base/golang-alpine:1.22-2024q2 基础镜像,预装 goreleaser, golangci-lint, trivy 工具链。
企业级可观测性集成方案
在 main.go 初始化阶段注入 OpenTelemetry SDK,自动采集 HTTP/gRPC 指标并上报至内部 Prometheus + Grafana 栈:
| 组件 | 采集方式 | 数据目标 |
|---|---|---|
| HTTP Server | otelhttp.Middleware | /metrics (Prometheus) |
| Database | otelsql.Wrap | Jaeger tracing backend |
| Log Output | zerolog.With().Str(“service”, “payment-api”) | Loki 日志集群 |
安全合规强化实践
某证券类项目通过以下措施满足等保三级要求:
- 使用
govulncheck每日扫描go list -m all依赖树,阻断 CVE-2023-45856 等高危漏洞; - 所有生产二进制文件经
cosign sign签名,并在 Kubernetes Admission Controller 中通过kyverno验证镜像签名有效性; go test覆盖率强制 ≥ 82%,未达标 PR 自动拒绝合并。
多云环境下的环境抽象层
为适配 AWS EKS、阿里云 ACK 及私有 OpenShift 三套集群,抽象出 envconfig 包,通过 ENV_PROFILE=prod-aws 环境变量动态加载配置:
func LoadConfig() *Config {
switch os.Getenv("ENV_PROFILE") {
case "prod-aws":
return loadAWSConfig()
case "prod-alicloud":
return loadAliCloudConfig()
default:
panic("unsupported ENV_PROFILE")
}
}
该机制支撑了 17 个微服务在 3 种云平台上的零代码变更部署。
CI/CD 流水线分层治理模型
flowchart LR
A[Git Push] --> B[Pre-Commit Hook<br/>gofmt + govet]
B --> C[PR Triggered]
C --> D[Stage 1: Unit Test + Coverage]
C --> E[Stage 2: Dependency Scan]
D & E --> F{Coverage ≥ 82%?}
F -->|Yes| G[Stage 3: Integration Test on Kind Cluster]
F -->|No| H[Reject PR]
G --> I[Stage 4: Build & Sign Image]
I --> J[Deploy to Staging] 