业务故事站
P3 Apollo

安全合规:SBOM、漏洞扫描与策略门禁

制品上线前,安全合规工程师要回答三个问题:里面有什么(SBOM)、有没有已知漏洞(扫描)、能不能放行(策略门禁)。Apollo 把这三个答案做成了内置的轻量能力,配合 bundle 签名信任锚与合规报告,让供应链安全"左移"到部署之前。看完这 4 个故事,你就能自己走通"建制品 → SBOM → 扫描 → 门禁 → 合规报告"的完整闭环。

安全合规 平台运维 应用开发 SBOM 漏洞扫描 SecurityGate 共 4 个故事

能 / 不能速览

✅ 这个主题能做
  • 为制品生成 SBOM 物料清单(SPDX 2.3 / CycloneDX 1.5),同制品重复生成幂等覆盖
  • 触发内置漏洞扫描:明文密钥 / latest 标签 / 缺 sha256 / 未签名 bundle 四类规则 + 内置漏洞库按包名版本匹配
  • 安全策略引擎:6 类规则(deny_latest_tag / require_signature / critical_vuln / no_plaintext_secret / require_resource_limits / custom)+ 5 个内置模板
  • 安全门禁 SecurityGate:决策 deny 返回 403,并已接入 GitOps 同步激活前自动评估(部署左移)
  • 项目合规报告:策略数 / 评估决策分布 / 扫描制品数 / 漏洞条目数
⛔ 这个主题做不了
  • 扫描器是内置模拟(zy_builtin),漏洞库仅 7 条示例,不是真实 Trivy,不能代替真实扫描
  • SBOM 是自研生成器,基于制品元数据,不是真实 Syft 的完整依赖树
  • OPA(Rego) 策略语言未实现,策略是自研 Go 规则引擎
  • 门禁当前拦截 GitOps 同步激活路径;部署编排(rollout)侧直接门禁仍属规划
  • 无传输层 TLS、无 KMS/Vault 托管密钥(生产需自行加固)

适用角色

本主题面向三个角色:

  • 安全合规工程师(刘经理):核心使用者——生成 SBOM、触发扫描、维护策略、做门禁评估、看合规报告。
  • 平台运维 / SRE(陈工):operator 角色可执行扫描与门禁评估(security:execute),但不可改策略。
  • 应用开发(阿强):把制品登记进仓库、补 sha256 / 修复版本,配合安全侧把漏洞清零。

能力速览(能做什么)

SBOM 物料清单

POST /artifacts/:id/sbom 生成 SPDX 2.3 / CycloneDX 1.5;同一制品仅一行,重复生成覆盖更新。

漏洞扫描

POST /artifacts/:id/scan:内置 4 条规则 + 7 条示例 CVE,回写制品扫描状态,结果落 data/security/reports。

安全策略引擎

security_policies 表定义规则,Evaluate 汇总 allow/warn/deny;每次评估落审计轨迹。

安全门禁 SecurityGate

deny 决策返回 403(带 violations 明细);已接入 GitOps Sync 激活前自动评估,deny → 保持 draft + sync failed。

合规报告

GET /projects/:id/compliance-report:策略数 / 启用数 / 近 30 天评估与决策分布 / 扫描与漏洞统计。

审计留痕

SECURITY_SBOM_GENERATE / SECURITY_SCAN_RUN / SECURITY_POLICY_CREATE / SECURITY_GATE_EVALUATE 等事件落审计日志。

调整指南(怎么调整)

  • 换 SBOM 格式:生成时传 format=cyclonedx(缺省 spdx);同一制品切换格式会覆盖旧记录,不会并存。
  • 调扫描规则命中:给制品补 sha256、去掉 latest 标签、为 bundle 填 signer_id,可消除 RULE-NO-SHA256 / RULE-LATEST-TAG / RULE-UNSIGNED-BUNDLE。
  • 改策略严重级:severity=deny 的违规直接拦截,warn 只提示不拦截;模板应用后可在"安全策略"Tab 编辑。
  • 限定生效环境:策略的 Environments 标签限定环境(空=全部环境生效)。
  • 看门禁结果:门禁拒绝时返回 decision=deny 与 violations 明细,按违规策略逐条修复再重新评估。

做得好的场景

"部署前安全检查"被做成了看得见、可审计、能拦截的闭环:
  • 上线前发现漏洞:制品扫出 critical 漏洞,门禁在部署前拦下,而不是上线后救火。
  • 供应链透明:SBOM + 扫描报告 + 策略评估,等保/SOC 2 审计时有据可查。
  • 安全策略一处定义处处生效:5 个内置模板一键应用,deny/warn 分级,环境限定生效。
  • 左移门禁自动执行:GitOps 同步激活前自动评估,人工忘记检查也不漏。

限制与不足

以下是明确的边界,使用前先知道:
  • 内置模拟扫描器:漏洞库仅 7 条示例,规则是启发式检查,不能代替真实 Trivy/Grype。
  • SBOM 基于制品元数据:不是真实依赖树扫描,组件级依赖不展开。
  • OPA(Rego) 未实现:策略是自研规则枚举,不支持 Rego 策略语言。
  • 门禁覆盖范围:已接入 GitOps Sync 激活路径;部署编排(rollout)侧与流水线 deploy 动作的直接门禁属规划。
  • 机密字段用 AES-256-GCM:design 承诺的 SM4 机密字段回迁是规划(平台 SM4 底座已就绪)。

场景故事

故事 1 生成 SBOM:上线前先回答"这个包里有什么"
背景
刘经理是安全合规工程师。明天要上线的 nginx 制品(版本 1.24.0)需要一份物料清单,审计要核对"里面到底有什么、版本号是什么"。他登录 Apollo 安全合规页(/apollo/security),在"扫描与漏洞"Tab 里先确认制品存在,然后点"生成 SBOM"。
传统做法对比
以前物料清单靠开发在 README 里手写,版本一换清单就过时;等审计来查时,开发要翻部署记录逐条对版本,一次核对少说半天。
角色
刘经理(安全合规工程师,需 security:execute 权限,operator/developer 以上均可)。
操作步骤
  1. 登录 Apollo(端口 18082,admin / admin1),进入"安全合规"页
  2. 先登记制品:POST /api/v1/projects/1/artifacts,name=nginx、version=1.24.0、type=docker_image
  3. 选中该制品,点击"生成 SBOM"(POST /api/v1/artifacts/:id/sbom)
  4. 查看返回的 SBOM 记录与原文
系统响应
生成接口返回:
POST /api/v1/artifacts/1/sbom
{ "code": 0, "data": {
  "artifact_id": 1, "format": "spdx",
  "sbom_data": { "spdxVersion": "SPDX-2.3",
    "name": "sbom-nginx-1.24.0",
    "documentNamespace": "https://apollo.zytech.local/sbom/1/nginx/1.24.0",
    "packages": [{ "name": "nginx", "versionInfo": "1.24.0",
      "checksums": [{ "algorithm": "SHA256", "checksumValue": "..." }] }] },
  "generated_at": "..." }, "request_id": "req_..." }
结果洞察
SBOM 包含制品名、版本、sha256 校验和与命名空间,符合 SPDX 2.3 结构,可以直接给审计当凭据。同一制品重复生成或切换格式(cyclonedx)都会整行覆盖——每个制品永远只有一份最新清单,不会出现"两份对不上"。
调整建议
制品没填 sha256 时 SBOM 的 checksums 为空,建议登记制品时就带上 sha256;审计要求 CycloneDX 格式时在生成请求里传 format=cyclonedx。
动手试一试
登录:http://127.0.0.1:18082,账号 admin / admin1。页面路径:Apollo → 安全合规 /apollo/security → Tab"扫描与漏洞"。输入内容:POST /api/v1/projects/1/artifacts 登记 nginx 1.24.0,然后 POST /api/v1/artifacts/:id/sbom。预期结果:返回 artifact_id=1、format=spdx、sbom_data.spdxVersion="SPDX-2.3"。
限制提示
SBOM 是自研生成器,基于制品元数据(name/version/sha256),不是真实 Syft 扫描出的完整依赖树——审计口径要与实现对齐,不要把它当依赖清单用。
故事 2 漏洞扫描:nginx 1.24.0 命中 critical 漏洞
背景
SBOM 生成完,刘经理点"触发扫描"(POST /api/v1/artifacts/1/scan),想在上线前确认 nginx 1.24.0 有没有已知漏洞。扫描器是内置的(标识 zy_builtin),会跑内置规则检查 + 内置小型漏洞库匹配。
传统做法对比
以前要靠人工去 NVD / 厂商公告逐个比对版本号,一天下来不一定找全;更常见的是"先上线、出事再补"——等漏洞曝光时,这个版本已经在生产跑了几个星期。
角色
刘经理(安全合规,security:execute 触发扫描)+ 阿强(应用开发,随后按报告升级版本)。
操作步骤
  1. 在制品列表选中 nginx 1.24.0
  2. 点击"触发扫描"(POST /api/v1/artifacts/1/scan)
  3. 查看返回的扫描报告状态与分级计数
  4. 到"制品漏洞"视图查看 findings 明细
系统响应
扫描返回:
POST /api/v1/artifacts/1/scan
{ "code": 0, "data": {
  "artifact_id": 1, "scanner": "zy_builtin",
  "status": "vulnerable",
  "total_critical": 1, "total_high": 0,
  "total_medium": 0, "total_low": 0,
  "scan_summary": { "total_findings": 1, "critical": 1,
    "rules_triggered": ["CVE-2024-27080"], "scanner": "zy_builtin" },
  "scan_result_path": "data/security/reports/report_1.json",
  "scanned_at": "..." }, "request_id": "req_..." }
// findings:CVE-2024-27080(critical),受影响包 nginx 1.24.0,修复版本 1.24.1
结果洞察
内置漏洞库按"包名+版本"匹配到 nginx CVE-2024-27080(critical),修复版本 1.24.1。报告同时回写了制品的 vulnerability_scan_status=vulnerable,项目漏洞视图会把它标红,谁看谁知道"这个制品不能直接上"。
调整建议
把"升级到 1.24.1"作为修复单发给阿强;同时核对制品 metadata 里有没有 password=/secret= 之类的明文,那会命中 RULE-PLAINTEXT-SECRET(high)——顺手把 sha256 补全,避免额外的 RULE-NO-SHA256 低风险噪音。
动手试一试
登录:admin / admin1。页面路径:/apollo/security → Tab"扫描与漏洞"。输入内容:登记 nginx 1.24.0 后 POST /api/v1/artifacts/1/scan。预期结果:status=vulnerable、total_critical=1、findings 含 CVE-2024-27080(修复版本 1.24.1)。
限制提示
扫描器是内置模拟,漏洞库只有 7 条示例 CVE,覆盖范围有限——只适合演示与基线检查,生产上线请接入真实 Trivy/Grype(规划中)。
故事 3 安全门禁:带 Critical 漏洞的制品被部署前拦下
背景
扫描确认 nginx 1.24.0 带 critical 漏洞,但阿强说"修复要下周,这周先上线顶着"。刘经理不答应——他先应用内置模板"Critical 漏洞拒绝"(deny 级),然后对制品做一次门禁评估,演示部署会被拦下来。
传统做法对比
以前"先上线顶着"就是放行,安全团队事后补流程,漏洞窗口期最长可达数周;门禁把"部署前必须无 critical 漏洞"变成硬约束,谁也不能口头放行。
角色
刘经理(安全合规,security:write 建策略 + security:execute 评估门禁);陈工(平台运维,operator 角色,可执行扫描/门禁但不能改策略)。
操作步骤
  1. 进入"安全策略"Tab,查看模板清单(GET /api/v1/security-policies/templates)
  2. 应用模板:POST /api/v1/security-policies/templates/Critical%20漏洞拒绝/apply?project_id=1
  3. 打开"安全门禁手动评估"表单,提交带 critical 漏洞的资源快照
  4. 观察 deny 决策与违规明细,再提交无漏洞快照对比 allow
系统响应
带 critical 漏洞的评估被拒绝:
POST /api/v1/security-gate/evaluate
{ "project_id": 1, "resource_type": "artifact", "resource_id": "1",
  "resource_data": { "tags": ["v1.24.0"], "sha256": "abc123",
    "vulns": [{ "severity": "critical" }] } }
→ 403 { "code": 40301,
  "message": "部署被安全策略拒绝: Critical 漏洞拒绝: 制品存在 critical 级漏洞",
  "data": { "decision": "deny", "policy_count": 1,
    "violations": [{ "policy_name": "Critical 漏洞拒绝",
      "rule_type": "critical_vuln", "severity": "deny",
      "reason": "制品存在 critical 级漏洞" }] } }
去掉 vulns 重新评估 → decision=allow。
结果洞察
门禁决策语义清晰:任一 deny 策略命中 → deny(403);无 deny 有 warn → warn;否则 allow。拒绝时接口同时回传 violations 明细,前端能展示"是哪条策略、什么原因"而不是一句干巴巴的拒绝文案。每次评估都落审计轨迹(SECURITY_GATE_EVALUATE)。
调整建议
除手动评估外,门禁已接入 GitOps 同步激活前自动评估:deny 时声明保持 draft、整次 sync 置 failed——这就是"部署左移"。正式环境建议把"禁止生产 latest 标签""部署必须签名""禁止明文密钥"等模板一起应用,形成完整基线。
动手试一试
登录:admin / admin1。页面路径:/apollo/security → Tab"合规总览"门禁表单 / Tab"安全策略"。输入内容:应用"Critical 漏洞拒绝"模板 → 带 vulns critical 评估。预期结果:403,decision=deny、violations 列出"Critical 漏洞拒绝";去掉 vulns 后评估返回 allow。
限制提示
门禁当前自动拦截的是 GitOps 同步激活路径(手动/Webhook/cron/流水线 DeployTrigger 全入口);部署编排(rollout)侧的直接门禁仍属规划。策略引擎是自研规则,OPA(Rego) 未实现。
故事 4 合规报告:项目安全态势一页看清
背景
季度安全例会,刘经理要给管理层和审计交一份"default 项目最近的安全态势":有多少策略在生效、评估拦了几次、扫了多少制品、还有多少漏洞挂着。他直接打开合规总览 Tab。
传统做法对比
以前安全汇报靠手工汇总 Excel,扫描结果、策略、审计三张表对不齐,数字口径各说各话;现在一次接口调用拿到统一口径的汇总数,汇报当场出数据。
角色
刘经理(安全合规,security:read 即可看报告);平台管理员(确认账号有权限点,viewer 以上都能看)。
操作步骤
  1. 打开"合规总览"Tab(或直接 GET /api/v1/projects/1/compliance-report)
  2. 查看顶部指标卡:策略数 / 启用策略 / 评估次数 / deny / warn / allow
  3. 查看扫描制品数 / 含漏洞制品 / 漏洞条目数
系统响应
合规报告返回:
GET /api/v1/projects/1/compliance-report
{ "code": 0, "data": {
  "project_id": 1,
  "total_policies": 1, "enabled_policies": 1,
  "evaluations_30d": 2, "allow_count": 1,
  "denied_count": 1, "warn_count": 0,
  "scanned_artifacts": 1, "vulnerable_artifacts": 1,
  "vuln_count": 1 }, "request_id": "req_..." }
前端按字段渲染成 9 张指标卡,一目了然。
结果洞察
这份报告把"策略生效情况、门禁拦了几次、还有多少带漏洞制品"压缩到一张图上:denied_count=1 说明门禁确实拦过一次,vulnerable_artifacts=1 说明还有待修复项。等阿强把 nginx 升到 1.24.1 重新扫描后,报告数字会自动更新——汇报口径和数据一致。
调整建议
把合规报告纳入例行检查:每次发版前看一次 vulnerable_artifacts 是否归零;deny 次数上升说明策略在起作用,但也要警惕"策略过严拖慢交付",可把某些项从 deny 降到 warn 观察。
动手试一试
登录:admin / admin1。页面路径:/apollo/security → Tab"合规总览"。输入内容:GET /api/v1/projects/1/compliance-report。预期结果:返回 total_policies / enabled_policies / evaluations_30d / allow_count / denied_count / scanned_artifacts / vulnerable_artifacts / vuln_count 等指标。
限制提示
评估与扫描统计口径来自内置模拟扫描器与策略评估审计(近 30 天窗口),deny 数据只统计经门禁/评估产生的记录;真实扫描器接入后数据会变,口径以当时代码为准。

常见问题

SBOM 和 bundle 签名是什么关系?

bundle 签名(SM3 清单 + SM2 签名 + 白名单信任锚)回答"这个包是谁打的、有没有被改";SBOM 回答"这个包里面有什么、什么版本"。两者互补:签名管来源与完整性,SBOM 管物料成分,都是供应链安全的组成部分。bundle 签名详见"Bundle 签名与验签"主题。

扫描器是真实的 Trivy 吗?

不是。内置扫描器标识 zy_builtin,漏洞库只有 7 条示例 CVE(nginx/log4j/openssl/spring-core/jackson/guava/mysql),加上 4 条启发式规则(明文密钥/latest/缺 sha256/未签名 bundle)。它用于演示、演示基线检查和规则联动,真实生产请等真实 Trivy 接入(规划中)。

SecurityGate 为什么返回 403?

门禁决策为 deny 时返回 403 AUTHZ_ERROR,同时回传 decision/violations 明细(哪条策略、什么原因),前端可精确展示拦截原因。allow/warn 则正常返回结果。另外门禁已接入 GitOps 同步激活前自动评估:deny 时声明保持 draft、整次 sync 置 failed。

为什么 OPA(Rego) 没实现?

OPA(Rego) 需要引入外部策略引擎,与"单机优先、不过度封装"的路线不符。当前用自研 Go 规则(RuleType 枚举 + Evaluate 汇总)覆盖了常见安全基线;若未来需要复杂策略语言,可在规划中接入 OPA。

扫描结果会清空吗?

不会。扫描报告、漏洞条目、策略、评估轨迹都持久保存在平台库(vulnerability_reports / vulnerability_findings / security_policies / policy_evaluation_results);同一制品重复扫描会生成新报告,查询取最近一次。演示制品需要自己创建。

主题小结

一句话:安全合规 = SBOM(里面有什么)+ 漏洞扫描(有没有已知漏洞)+ 策略门禁(能不能放行)+ 合规报告(态势如何)。四件套全部内置、可审计、门禁已左移到 GitOps 同步激活前。记住边界:扫描器是内置模拟(非真实 Trivy)、SBOM 是元数据生成(非 Syft)、OPA(Rego) 未实现、rollout 侧门禁仍规划。