Posted in

【Go签名安全实战指南】:20年专家总结的5大高危签名漏洞及防御方案

第一章:Go签名安全的核心概念与威胁模型

数字签名在Go生态中是保障软件供应链完整性与来源可信性的关键机制,其核心在于利用非对称密码学(如RSA、ECDSA)实现可验证的“签署-验签”闭环。Go官方工具链(go signgo verify)及第三方库(如cosignsigstore)均依赖X.509证书、OIDC身份和透明日志(Rekor)构建端到端信任链。

签名生命周期中的关键实体

  • 签名者(Signer):持有私钥的开发者或CI系统,需严格隔离密钥存储(推荐使用硬件安全模块HSM或云KMS);
  • 签名对象(Artifact):包括二进制文件、容器镜像、源码归档(.zip/.tar.gz)及Go模块校验和(go.sum);
  • 验证者(Verifier):运行go verify或集成Sigstore客户端的终端用户,依赖可信根证书与时间锚点判断签名有效性。

典型威胁模型分析

威胁类型 攻击面示例 缓解策略
私钥泄露 CI环境硬编码私钥被日志误输出 使用cosign generate-key-pair --kms azurekms://...调用云KMS托管密钥
伪造签名 攻击者篡改二进制后重签名(无绑定哈希) 强制绑定内容哈希:cosign sign --payload payload.json --signature sig.der binary
时间漂移攻击 验证端系统时钟偏差导致证书过期误判 启用RFC 3161时间戳服务:cosign sign --tlog-upload --timestamp-server https://rekor.sigstore.dev

Go原生签名验证实践

执行以下命令可验证已签名Go模块的完整性:

# 1. 下载模块并获取其校验和(自动触发cosign验证)
go mod download github.com/example/pkg@v1.2.3

# 2. 手动验签(需提前配置Sigstore信任根)
cosign verify \
  --certificate-identity "https://github.com/example/.github/workflows/ci.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/example/pkg:v1.2.3

该流程强制校验OIDC声明、证书链有效性及Rekor日志索引,确保签名行为发生在受信CI上下文中,而非本地伪造。

第二章:密钥管理不当引发的签名伪造漏洞

2.1 静态硬编码私钥的风险分析与go:embed安全替代实践

🔍 风险本质

硬编码私钥(如 PEM 文件内容直接写入 .go 源码)导致密钥随代码进入版本库、CI 缓存、容器镜像,极易泄露。Git 历史回溯、依赖扫描工具(truffleHog)可瞬间提取。

🛑 典型错误示例

// ❌ 危险:私钥明文嵌入源码
const privateKeyPEM = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDGq...[truncated]
-----END EC PRIVATE KEY-----`

逻辑分析:该字符串在编译期即固化进二进制,strings 命令可直接提取;go:embed 未启用时,无编译期校验或访问控制,且无法实现密钥轮换隔离。

✅ go:embed 安全实践

// ✅ 推荐:私钥置于 ./secrets/privkey.pem(非 Git 跟踪),构建时嵌入
import _ "embed"
//go:embed secrets/privkey.pem
var privateKeyPEM []byte

参数说明//go:embed 指令使文件在编译时读取为只读字节切片,不暴露路径、不参与运行时文件系统访问,规避 os.ReadFile 的权限与竞态风险。

📊 替代方案对比

方案 密钥可见性 构建时绑定 运行时依赖 Git 安全
硬编码字符串 ⚠️ 高(源码级)
os.ReadFile ⚠️ 中(需文件存在) 是(I/O) ⚠️(若误提交)
go:embed ✅ 低(仅二进制内) ✅ 是 ✅(目录可 .gitignore

2.2 RSA私钥权限失控导致的签名泄露:chmod误用与umask加固方案

常见误操作场景

开发人员常以 chmod 755 id_rsa 授权私钥,使组/其他用户可读——这直接导致 ssh-keygen -Y sign 签名能力被未授权进程滥用。

权限修复命令

# 严格限定:仅属主可读写,禁止执行(私钥无需x位)
chmod 600 ~/.ssh/id_rsa
# 验证结果
ls -l ~/.ssh/id_rsa  # 应输出:-rw------- 1 user user ...

逻辑分析:600 对应八进制 110 000 000,即 rw- --- ---755 错误引入 r-x 给 group/others,使 openssl pkey -in id_rsa -text 可被任意本地用户调用解密。

umask全局防护策略

场景 默认umask 安全umask 效果
新建私钥文件 0022 0077 创建即为 600(666 & ~077)
graph TD
    A[生成私钥] --> B{umask=0077?}
    B -->|是| C[文件权限=600]
    B -->|否| D[可能为644/664→风险]

2.3 ECDSA密钥重用与nonce复用漏洞:crypto/ecdsa源码级审计与safeNonce封装

ECDSA签名安全性严格依赖于每次签名时唯一且不可预测的nonce(k)crypto/ecdsa标准库未内置nonce生成防护,直接调用Sign(rand io.Reader, priv *PrivateKey, hash []byte)时若rand复用或熵不足,将导致私钥泄露。

nonce复用的灾难性后果

当两次签名使用相同k对不同消息m₁、m₂,则:

s₁ = k⁻¹(H(m₁) + d·r) mod n  
s₂ = k⁻¹(H(m₂) + d·r) mod n  
→ d = r⁻¹·(s₁·H(m₁) − s₂·H(m₂))·(s₂ − s₁)⁻¹ mod n

safeNonce安全封装设计

func safeNonce(rand io.Reader) ([]byte, error) {
    k := make([]byte, 32)
    if _, err := io.ReadFull(rand, k); err != nil {
        return nil, err // 阻断短读,强制全熵填充
    }
    return k, nil
}

该函数强制32字节全量读取,规避crypto/rand.Reader在虚拟机等低熵环境下的Read()截断风险,确保k满足RFC 6979确定性派生前提。

风险类型 检测方式 安全对策
nonce复用 签名日志k值哈希比对 safeNonce全量读取
私钥重用 ecdsa.PrivateKey.D内存扫描 运行时零化敏感字段
graph TD
    A[Sign call] --> B{safeNonce?}
    B -->|Yes| C[32B full-read]
    B -->|No| D[rand.Reader.Read]
    C --> E[Verify k ≠ 0 ∧ k < n]
    D --> F[潜在短读/重复k]

2.4 密钥轮转缺失引发的长期签名失效:基于etcd的动态密钥分发与gin中间件集成

当签名密钥长期未轮转,JWT 或 HMAC 签名将因密钥泄露或过期而批量失效。传统静态密钥硬编码方式无法支撑零停机更新。

动态密钥加载机制

通过 etcd Watch API 实时监听 /auth/keys/current 路径变更,触发内存密钥缓存热替换:

// gin 中间件:从 etcd 加载并缓存当前活跃密钥
func KeyMiddleware(client *clientv3.Client) gin.HandlerFunc {
    var currentKey []byte
    watchCh := client.Watch(context.Background(), "/auth/keys/current")
    go func() {
        for wresp := range watchCh {
            for _, ev := range wresp.Events {
                if ev.Type == clientv3.EventTypePut {
                    currentKey = ev.Kv.Value // 二进制密钥(如 32B AES-256)
                }
            }
        }
    }()
    return func(c *gin.Context) {
        c.Set("signing_key", currentKey)
        c.Next()
    }
}

逻辑分析currentKey 为全局只读引用,避免锁竞争;Watch 启动 goroutine 异步更新,确保中间件执行无阻塞。ev.Kv.Value 是 etcd 存储的原始密钥字节,要求服务端预置为 Base64 解码后二进制(非明文字符串)。

密钥元数据管理(etcd 结构)

路径 值类型 说明
/auth/keys/current []byte 当前生效密钥(用于签名校验)
/auth/keys/next []byte 待切换密钥(灰度验证中)
/auth/keys/rotation_ts string ISO8601 时间戳,标记轮转生效时间

签名校验流程

graph TD
    A[HTTP Request] --> B{gin.KeyMiddleware}
    B --> C[读取 currentKey]
    C --> D[解析 JWT Header.Payload.Signature]
    D --> E[HS256 Verify with currentKey]
    E -->|失败且 next 存在| F[尝试用 nextKey 重验]
    E -->|成功| G[放行]

2.5 HSM集成盲区:使用go-pkcs11实现硬件级签名卸载与错误处理兜底策略

HSM集成常忽视会话异常中断、令牌不可用或操作超时等“静默失败”场景,导致签名请求无感知挂起或降级失效。

兜底签名流程设计

  • 优先调用HSM执行C_Sign(),超时阈值设为800ms
  • 捕获CKR_DEVICE_REMOVEDCKR_SESSION_CLOSED等关键错误码
  • 自动降级至内存中软签名(仅限审计白名单场景)
// 初始化PKCS#11会话并签名(带上下文超时)
ctx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
defer cancel()
sig, err := session.Sign(ctx, mech, privKey, digest)
if errors.Is(err, pkcs11.Error(CKR_DEVICE_REMOVED)) {
    return fallbackSign(digest) // 审计日志+指标上报
}

该代码通过context.WithTimeout强制中断阻塞调用;pkcs11.Error类型断言精准识别HSM物理离线态;fallbackSign需经RBAC校验且记录signature_fallback_total{reason="device_removed"}指标。

错误码映射与响应策略

PKCS#11错误码 建议动作 可恢复性
CKR_SESSION_CLOSED 重建会话 + 重试
CKR_TOKEN_NOT_PRESENT 切换备用HSM集群 ⚠️
CKR_FUNCTION_FAILED 中止并告警(硬件故障)
graph TD
    A[发起签名] --> B{HSM可用?}
    B -->|是| C[执行C_Sign]
    B -->|否| D[触发降级]
    C --> E{成功?}
    E -->|是| F[返回签名]
    E -->|否| G[解析CKR_*码]
    G --> H[执行对应兜底策略]

第三章:签名算法选型与实现缺陷漏洞

3.1 SHA-1/MD5哈希碰撞攻击在crypto/signer中的实际复现与go.mod依赖树扫描防御

复现签名验证绕过场景

以下代码模拟使用 crypto/signer 接口但底层哈希被降级为 MD5 的脆弱签名验证逻辑:

// vuln_signer.go —— 错误地允许MD5作为签名哈希算法
func SignWithMD5(priv *rsa.PrivateKey, data []byte) ([]byte, error) {
    hash := md5.New() // ⚠️ 禁用:MD5已不安全
    hash.Write(data)
    digest := hash.Sum(nil)
    return rsa.SignPKCS1v15(rand.Reader, priv, crypto.MD5, digest[:])
}

逻辑分析crypto.MD5 常量仅表示哈希标识符,不校验实际哈希实例强度;若 hash 实例为 MD5,即使 signer 接口声明支持 crypto.Hash,仍可生成可碰撞签名。参数 crypto.MD5 仅用于填充标识,不触发哈希强度检查。

依赖树扫描防御策略

使用 go list -m -json all 提取模块依赖树,结合规则引擎识别弱哈希调用:

检测项 触发条件 修复建议
crypto/md5 导入 模块源码中直接 import "crypto/md5" 替换为 crypto/sha256
crypto.MD5 常量使用 AST 中匹配 crypto.MD5 字面量 升级至 crypto.SHA256

自动化检测流程

graph TD
    A[go list -m -json all] --> B[解析模块路径]
    B --> C[对每个模块执行 go list -f '{{.Imports}}']
    C --> D[静态扫描 import/crypto.md5 & crypto.MD5]
    D --> E[报告高风险节点并阻断 CI]

3.2 Ed25519签名未验证公钥有效性导致的跨域伪造:x/crypto/ed25519源码补丁与validator封装

Ed25519标准要求公钥必须是椭圆曲线上的有效点(即满足 $ y^2 = x^3 + 486662x^2 + x \mod p $),但 Go 标准库 x/crypto/ed25519Verify() 函数未校验公钥格式与群成员性,攻击者可构造恶意公钥实现跨域签名伪造。

公钥有效性验证缺失的危害

  • 任意32字节均可被当作“公钥”传入 Verify()
  • 非法点可能触发未定义行为或绕过签名逻辑

补丁核心逻辑(patch snippet)

// 在 Verify 前插入:
if !isValidEd25519PublicKey(pub) {
    return false
}

isValidEd25519PublicKey 调用 curve25519.NewDecoder().DecodePoint() 并检查是否为小阶点(如 1、2、4、8 阶)及是否在主子群中。参数 pub 必须为 32 字节且解码后满足 point.IsOnCurve() && point.Order() == CurveOrder

封装 validator 接口

方法 输入 输出 说明
ValidatePublicKey []byte (32B) error 检查点有效性、阶、压缩格式
VerifyWithValidation pub, msg, sig bool 组合公钥校验 + 签名验证
graph TD
    A[VerifyWithValidation] --> B{ValidatePublicKey?}
    B -->|false| C[return false]
    B -->|true| D[ed25519.Verify]
    D --> E[return result]

3.3 RSA-PSS参数配置错误(salt长度不足)引发的Oracle攻击:pss.Options深度校验与自动化检测工具开发

RSA-PSS签名中,salt length 若固定为 或远小于 hash.Size()(如 SHA-256 下 salt

常见危险配置示例

opts := &rsa.PSSOptions{
    SaltLength: 0, // ❌ 危险:无盐值,退化为确定性签名
    Hash:       crypto.SHA256,
}

逻辑分析:SaltLength = 0 导致 MGF1 掩码完全由消息哈希和公共参数决定,签名可被重复生成;攻击者通过验证服务返回的 invalid signature / valid 差异,可逐步恢复私钥。

安全阈值对照表

Hash 算法 最小推荐 SaltLength 对应字节数
SHA-1 rsa.PSSSaltLengthAuto 20
SHA-256 rsa.PSSSaltLengthAuto 32

自动化检测核心逻辑

func isPSSTooWeak(opts *rsa.PSSOptions) bool {
    if opts.SaltLength == 0 { return true }
    min := opts.Hash.Size() // NIST SP 800-56B 要求 salt ≥ hash size
    return opts.SaltLength < min
}

该函数在 TLS 握手前、证书签名验证链入口处注入,实现零侵入式策略拦截。

第四章:签名上下文与协议层绕过漏洞

4.1 JWT签名剥离攻击:golang-jwt库中ParseUnverified误用场景与claims绑定签名验证模式重构

风险根源:ParseUnverified 的语义陷阱

该函数跳过签名校验,仅解析载荷——若后续逻辑*错误地信任其返回的 `TokenClaims字段**,攻击者可构造无签名或弱签名(如none` 算法)的 JWT 绕过认证。

典型误用代码

token, _ := jwt.ParseUnverified(tokenString, &MyClaims{}) // ❌ 危险:未验证签名即使用
claims := token.Claims.(*MyClaims)
if claims.UserID > 0 { // ✅ 但此判断完全依赖未验证数据!
    grantAccess(claims.UserID)
}

逻辑分析ParseUnverified 不校验 Signature 字段,也不检查 Header.Alg 是否被篡改。参数 tokenString 可为任意伪造 JWT(如 eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxfQ.),claims.UserID 完全不可信。

安全重构:Claims 与 Signature 强绑定验证

keyFunc := func(t *jwt.Token) (interface{}, error) {
    if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
    }
    return []byte("secret"), nil
}
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, keyFunc)

参数说明keyFunc 在签名验证前动态提供密钥,并强制校验 SigningMethodParseWithClaims 确保 Claims 仅在签名有效时被解码并赋值。

修复前后对比

维度 ParseUnverified ParseWithClaims + keyFunc
签名校验 跳过 强制执行
Claims 可信度 0%(纯用户输入) 100%(绑定签名完整性)
算法白名单 可在 keyFunc 中显式校验
graph TD
    A[收到JWT字符串] --> B{调用 ParseUnverified?}
    B -->|是| C[解析Claims → 不可信]
    B -->|否| D[调用 ParseWithClaims]
    D --> E[验证Header.Alg]
    E --> F[执行keyFunc校验算法/密钥]
    F --> G[验证Signature]
    G -->|通过| H[安全解码Claims]

4.2 HTTP签名头注入:net/http.Header签名覆盖漏洞与signer.HeaderSigner安全抽象设计

漏洞成因:Header可变性导致签名失效

net/http.Headermap[string][]string 的别名,其底层 map 可被多次 Set()/Add() 修改——签名若仅基于初始 Header 快照生成,后续篡改将绕过验证。

危险示例与修复对比

// ❌ 危险:签名后仍可修改
h := http.Header{}
h.Set("X-Signature", "invalid")
sig := sign(h) // 基于当前值签名
h.Set("X-Signature", "forged") // 签名失效!

// ✅ 安全:HeaderSigner 封装不可变视图
signer := &signer.HeaderSigner{Header: h.Clone()} // 克隆+只读封装
sig = signer.Sign() // 签名绑定克隆副本

逻辑分析:h.Clone() 返回新 map 实例,避免原始 Header 被污染;HeaderSigner.Sign() 内部对 Header 做排序归一化(如按 key 字典序序列化),确保签名一致性。参数 h 为待签名原始 Header,Clone() 是 Go 1.19+ 引入的安全克隆方法。

安全抽象核心约束

约束项 说明
不可变快照 签名前强制克隆并冻结 Header
键标准化 忽略大小写、合并重复键值
签名绑定上下文 包含时间戳、请求路径等防重放字段
graph TD
    A[原始Header] --> B[Clone()]
    B --> C[Key标准化]
    C --> D[序列化为CanonicalBytes]
    D --> E[Sign with PrivateKey]

4.3 Webhook签名时效性缺失:时间漂移容忍窗口与crypto/rand生成nonce的时序安全实践

Webhook签名若仅依赖time.Now().Unix()而忽略客户端-服务端时钟偏移,将导致合法请求被误拒。

时间漂移容忍窗口设计

服务端应校验签名中 t 参数是否落在 [now−maxDrift, now+maxDrift] 区间(推荐 maxDrift = 300s):

const maxDrift = 300 // seconds
t, _ := strconv.ParseInt(query.Get("t"), 10, 64)
if t < time.Now().Unix()-maxDrift || t > time.Now().Unix()+maxDrift {
    return errors.New("timestamp outside drift window")
}

逻辑分析:t 为客户端签名时戳,需在服务端当前时间±5分钟内。maxDrift 需权衡安全性(防重放)与可用性(容忍NTP偏差);硬编码值应通过配置中心动态管理。

nonce 的时序安全生成

必须使用 crypto/rand 避免可预测性:

nonce := make([]byte, 16)
_, _ = rand.Read(nonce) // 不可用 math/rand!

参数说明:16字节 nonce 提供 128 位熵,配合 t 构成唯一签名上下文,阻断重放攻击。

组件 安全要求 违规示例
时间戳 t Unix 秒级,含漂移容错 使用毫秒或无校验
Nonce CSPRNG 生成,≥128 bit uuid.New()time.Now().String()
签名算法 HMAC-SHA256 + t+nonce+body 仅签 body
graph TD
    A[Client: t=1717023456, nonce=... ] --> B[Sign t+nonce+body]
    B --> C[Send t, nonce, sig, body]
    C --> D[Server: check t ∈ [now±300]]
    D --> E[Verify HMAC with same t+nonce+body]

4.4 签名与业务逻辑解耦导致的重放攻击:基于redis分布式锁+HMAC-SHA256的请求唯一性签名链设计

当签名验证与业务处理分离时,攻击者可截获合法请求并多次重放——尤其在支付、积分变更等幂等性敏感场景中风险陡增。

核心防御思路

构建「时间戳 + 随机nonce + 业务摘要」三元签名链,结合Redis原子锁实现单次消费保障:

import hmac, hashlib, time, redis
r = redis.Redis()

def sign_request(payload: dict, secret: str) -> str:
    ts = int(time.time() * 1000)
    nonce = "a1b2c3d4"  # 实际应由客户端生成并保证全局唯一
    body_hash = hashlib.sha256(str(payload).encode()).hexdigest()[:16]
    sign_str = f"{ts}|{nonce}|{body_hash}"
    signature = hmac.new(secret.encode(), sign_str.encode(), hashlib.sha256).hexdigest()
    return f"{ts}.{nonce}.{signature}"

逻辑分析ts 控制时效(如5分钟窗口),nonce 绑定单次请求生命周期,body_hash 防篡改。签名后需立即用 SET key value EX 300 NX 写入Redis,失败即拒绝请求。

签名验证流程

graph TD
    A[接收请求] --> B{解析ts/nonce/signature}
    B --> C[校验ts是否超时]
    C --> D[尝试SETNX写入nonce为key]
    D -->|成功| E[执行业务逻辑]
    D -->|失败| F[拒绝重放]

关键参数对照表

参数 作用 示例值 安全要求
ts 请求时间戳 1717023456000 ≤ ±300s偏差
nonce 一次性随机串 a1b2c3d4 全局唯一,长度≥8
EX 300 Redis锁过期 300秒 ≥业务最大处理耗时

第五章:Go签名安全演进趋势与工程化落地建议

签名算法从SHA-1向Ed25519的渐进迁移

某金融级API网关在2023年完成签名体系重构,将原有基于hmac-sha1的请求签名机制全面升级为Ed25519公钥签名。迁移过程采用双签并行模式:服务端同时校验X-Signature-HMACX-Signature-ED25519头字段,客户端分批次灰度切换。关键改造点包括:使用golang.org/x/crypto/ed25519生成密钥对(非crypto/rsa),签名时强制绑定Content-MD5X-Request-ID、时间戳(精度至毫秒)及规范化HTTP方法+路径+查询参数;验证阶段引入时钟漂移容错(±15s)与重复请求ID缓存(Redis TTL 60s)。该方案使签名验签耗时降低42%,且彻底规避哈希长度扩展攻击风险。

零信任签名上下文注入实践

在Kubernetes Operator中实现签名上下文自动化注入:

func (r *AppReconciler) injectSignature(ctx context.Context, pod *corev1.Pod) error {
    secret, err := r.KubeClient.CoreV1().Secrets(pod.Namespace).Get(ctx, "signing-key", metav1.GetOptions{})
    if err != nil { return err }

    // 使用k8s service account token签名pod元数据
    jwtToken, _ := signPodMetadata(secret.Data["private.key"], pod)
    pod.Annotations["security.signatures.k8s.io"] = jwtToken

    return r.KubeClient.CoreV1().Pods(pod.Namespace).Update(ctx, pod, metav1.UpdateOptions{})
}

该机制使每个Pod启动时自动携带经集群CA签名的运行时身份凭证,下游服务通过k8s.io/client-go验证JWT签名链,实现无需预共享密钥的双向认证。

密钥生命周期自动化管理矩阵

阶段 工具链 Go SDK集成方式 审计要求
生成 HashiCorp Vault KMS vaultapi.NewClient().Logical().Write() HSM背书日志留存≥180天
分发 SPIFFE Workload API spiffeid.RequireTrustDomain() TLS双向mTLS强制启用
轮换 自研KeyRotator Controller controller-runtime.Manager Reconcile 滚动更新期间双密钥共存
销毁 AWS KMS ScheduleDelete kmsclient.ScheduleKeyDeletion() 销毁确认需双人审批

安全边界强化的签名中间件设计

采用Go 1.21+ net/httpHandlerFunc链式封装,在Gin框架中嵌入签名验证中间件:

func SignatureMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        sig := c.GetHeader("X-Signature")
        ts := c.GetHeader("X-Timestamp")
        if !isValidTimestamp(ts) {
            c.AbortWithStatusJSON(401, gin.H{"error": "invalid timestamp"})
            return
        }

        // 构建标准化签名原文:method+path+body-hash+ts+nonce
        bodyHash := sha256.Sum256(c.Request.Body)
        canonical := fmt.Sprintf("%s\n%s\n%x\n%s\n%s", 
            c.Request.Method, c.Request.URL.Path, bodyHash, ts, c.GetHeader("X-Nonce"))

        if !verifyEd25519Signature(canonical, sig, getPubKeyFromHeader(c)) {
            c.AbortWithStatusJSON(403, gin.H{"error": "signature verification failed"})
            return
        }
        c.Next()
    }
}

该中间件已部署于日均处理2.7亿次调用的支付清分系统,配合eBPF程序实时捕获异常签名流量特征,实现毫秒级熔断响应。

开源组件供应链签名验证流水线

在CI/CD流程中集成Cosign验证步骤:

flowchart LR
    A[Git Commit] --> B{Cosign Sign?}
    B -->|Yes| C[cosign sign --key cosign.key ./binary]
    B -->|No| D[Reject Build]
    C --> E[Upload to OCI Registry]
    E --> F[Production Deploy]
    F --> G{Verify on Node}
    G -->|cosign verify --key cosign.pub| H[Load Binary]
    G -->|Fail| I[Block Execution]

在 Kubernetes 和微服务中成长,每天进步一点点。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注