背景与价值容器镜像作为交付载体必须具备可验证来源与不可抵赖属性。结合Sigstore生态与准入门禁策略,能在构建与部署两端阻断未授权镜像。统一规范注册表白名单:仅允许受控 `https` 域名与仓库命名约定。摘要与签名:统一校验 `sha256` 摘要与 ES256 签名。透明日志:校验签名记录存在于透明日志并匹配主体。政策门禁:在CI与K8s Admission同时执行阻断策略,默认拒绝不合规。核心实现镜像标识解析与来源校验type ImageRef = { registry: string; repo: string; tag?: string; digest?: string }
const allowRegistries = new Set([
'https://registry-1.docker.io',
'https://ghcr.io',
'https://registry.company.example'
])
function parseImageRef(s: string): ImageRef | null {
const m = s.match(/^([^\/]+)\/([^:@]+(?:\/[A-Za-z0-9._-]+)*)(?::([A-Za-z0-9._-]+))?(?:@sha256:([a-f0-9]{64}))?$/)
if (!m) return null
return { registry: m[1], repo: m[2], tag: m[3], digest: m[4] }
}
function registryAllowed(reg: string): boolean {
try {
const u = new URL('https://' + reg)
return u.protocol === 'https:' && allowRegistries.has(u.origin)
} catch {
return false
}
}
摘要与签名验证(ES256)function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
function b64(b: ArrayBuffer): string { const u = new Uint8Array(b); let s=''; for (let i=0;i<u.length;i++) s+=String.fromCharCode(u[i]); return btoa(s) }
type Sig = { kid: string; alg: 'ES256'; sig: string; subject: string }
async function importPublicKey(spki: ArrayBuffer): Promise<CryptoKey> {
return crypto.subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify'])
}
async function verifyDigestSignature(digestHex: string, sig: Sig, pub: CryptoKey, expectedSubject: string): Promise<boolean> {
if (!/^([a-f0-9]{64})$/.test(digestHex)) return false
if (sig.alg !== 'ES256') return false
if (sig.subject !== expectedSubject) return false
const bin = Uint8Array.from(atob(sig.sig), c => c.charCodeAt(0)).buffer
const ok = await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, pub, bin, enc(digestHex))
return !!ok
}
透明日志校验(Rekor条目格式验证)type RekorEntry = { uuid: string; subject: string; digest: string }
function validRekorEntry(e: RekorEntry, expectedSubject: string, digestHex: string): boolean {
if (!/^[0-9a-f-]{36}$/.test(e.uuid)) return false
if (e.subject !== expectedSubject) return false
return e.digest === digestHex
}
CI与准入门禁策略type Policy = { requireSigned: boolean; allowedSubjects: Set<string> }
async function gate(image: string, sig: Sig | null, rekor: RekorEntry | null, pub: CryptoKey, policy: Policy): Promise<boolean> {
const ref = parseImageRef(image)
if (!ref) return false
if (!registryAllowed(ref.registry)) return false
if (policy.requireSigned) {
if (!ref.digest || !sig || !rekor) return false
if (!policy.allowedSubjects.has(sig.subject)) return false
const okSig = await verifyDigestSignature(ref.digest, sig, pub, sig.subject)
if (!okSig) return false
const okLog = validRekorEntry(rekor, sig.subject, ref.digest)
if (!okLog) return false
}
return true
}
落地建议构建产物固定 `sha256` 摘要并生成ES256签名,记录 `kid` 与主体。在CI验证签名与透明日志条目,并按主体白名单执行阻断策略。在K8s Admission复用同一策略,未签名或主体不匹配一律拒绝。注册表域名统一纳入白名单并强制 `https` 与仓库命名规范。验证清单镜像引用是否包含合法 `registry/repo:tag@sha256:digest`。注册表是否命中白名单且为 `https`。ES256签名是否与摘要完全匹配且主体在白名单内。透明日志条目是否存在且 `uuid/subject/digest` 一致。

发表评论 取消回复