软件成分分析系统——项目经历
软件成分分析系统
**项目简介:**开发基于包管理器构建过程的软件成分分析系统,采用BuildScan动态检测技术,支持Java/JS/Python/Go多语言依赖 关系解析,实现漏洞风险识别、许可证合规分析、软件资产统一管理,并自动化生成符合行业标准的SBOM(软件物料清单)及 可视化安全报告。
**技术栈:**SpringBoot,PostgreSQL,Redis,RocketMQ,maven/gradle/npm/pip/go modules包管理器
主要功能:
提取项目中的配置文件,基于包管理器构建完整依赖树,并即时返回初步结果。
采用 RocketMQ 将组件信息采集、漏洞匹配和许可证识别任务异步拆分,并结合多线程并发处理,加速整体数据处理流程。
依据CPE规范构建组件标识,通过PostgreSQL的pg_trgm插件+GIN索引实现相似度高效匹配漏洞信息。
聚合多源许可证数据,分析权利/义务条款并构建冲突检测规则引擎。
生成符合CycloneDX/SPDX规范的软件物料清单,支持XML/JSON多格式输出
异步处理
在 SCA(软件成分分析)系统的异步设计中,我们利用 RocketMQ 进行解耦,将 组件查询、缺失组件爬取、漏洞匹配 这三个 I/O 密集型任务异步化,提高系统的吞吐量和并发能力。
具体来说,我们采用 消息队列 设计,将 解析依赖树后 的组件查询任务推送到 component-query-queue
,如果组件缺失,则发送爬取任务到 component-crawl-queue
,每一个组件查询完成后,再异步执行 vulnerability-match-queue
进行漏洞匹配。
在实现上,我们使用 RocketMQ 生产者推送任务,消费者监听队列 进行异步处理,并结合 线程池并发查询 以及 Redis 幂等性校验,确保任务高效执行,同时防止重复消费或数据不一致的问题。
这样,整个 SCA 任务执行过程能够更快响应,避免同步阻塞,提高系统扩展性和稳定性,同时也为后续增加更多语言或优化流程提供了良好的架构支持。
多语言框架
SCA 系统需要分析 Java、Python、JavaScript、Go 四种语言的项目,每种语言的处理方式不同,但总体流程相同,具体包括以下步骤:
- 上传项目
- 生成依赖树(执行包管理器命令)
- 解析依赖树,(然后将组件查询任务推送到
component-query-queue
,最后返回前端) - 查询组件信息,(将缺失组件爬取任务推送到
component-crawl-queue
,然后结束) - 爬取缺失组件信息,(同时匹配许可证),线程池处理,并将漏洞匹配任务推送到
vulnerability-match-queue
,然后结束 - 匹配组件的漏洞信息,结束
具体的,首先定义一个统一的sca处理器,和一个统一的消费者。然后定义不同语言的策略,和一个工厂用来根据语言生成策略。上传项目后,sca处理器根据语言,从工厂获得对应语言的策略,然后调用策略的方法生成依赖树、解析依赖树,然后将组件查询任务推送到 component-query-queue
,并返回前端。
消费者接受消息后,首先根据topic选择,
漏洞匹配
CPE
CPE(Common Platform Enumeration)是一个用于标识软件和硬件平台的标准化命名系统。CPE 使用特定格式来表示一个软件、硬件或操作系统的标识符。通常,CPE 标识符遵循类似于下面的格式:
1 | cpe:2.3:a:apache:http_server:2.4.49:*:*:*:*:*:*:* |
cpe:2.3
:表示使用的是 CPE 版本 2.3。a
:表示组件类型(软件/硬件/OS)apache
:厂商名。http_server
:产品名。2.4.49
:版本号。*:*:*:*:*:*:*
:更新版本、发行版、语言、软件类型、硬件架构、其他等
pg_trgm 插件
pg_trgm
插件是 PostgreSQL 中用于加速模糊匹配和相似度计算的工具,它使用 trigram(三元组)来将字符串分解为连续的三字符片段,并使用这些片段来计算两个字符串之间的相似度。通过这种方式,pg_trgm
可以帮助我们计算字符串间的相似度,即使它们存在拼写错误、字符交换或省略。
在这个过程中,pg_trgm
插件可以对 CPE 标识符进行拆解,以便通过类似的字符串或不完全匹配找到相似的漏洞信息。CPE 标识符可能会有一些拼写错误或不同版本的表达方式,因此使用 pg_trgm
插件能够有效捕捉到相似的组件信息。
具体的原理:例如对于hello
,可以分为{“ h”, “ he”, “hel”, “ell”, “llo”, “lo “},另一个字符串也同样划分后,通过计算 **两个三元组集合的交集 / 并集 **作为相似度。
GIN索引(倒排索引)
GIN(Generalized Inverted Index)是 PostgreSQL 的一种索引类型,用于加速处理包含多值数据的字段,如数组、文本搜索、JSONB 等。GIN 索引为每个值构建倒排索引,这使得查询速度显著提高,特别是在需要进行模糊匹配、全文搜索时。
倒排索引会记录每个值在哪些文档(或行)中出现。比如,对于一个文本字段,你的倒排索引可能看起来像这样:
word1 -> {1, 2, 5}
word2 -> {1, 3}
word3 -> {2, 4}
这个倒排结构允许快速定位包含某个单词的行。
整体流程
根据组件名、厂商、版本等信息构建自定义cpe,其中有含版本号和不含版本号两个cpe
根据自定义cpe,到cve_cpe表中进行相似度查询,找到所有对应的cve_cpe。
2.1. trgm将cpe拆解为一个个三元组
2.2. 每一个三元组都建立了倒排索引,可以快速定位到匹配的行
2.3. 再计算这些行的cpe与自定义cpe的相似度,筛选出相似度高的
对这些找到的cve_cpe进行召回,限定cpe中的单词至少需精准包含组件名称单词的半数以上以防止误判,以及检查组件版本是否处于规定的漏洞影响起始和终止版本之间
根据cve_cpe中的cve,到cve表中查到对应的漏洞
RocketMQ
顺序性
Rocketmq有两种顺序消费模式。
全局顺序(Global Order)
- 所有消息 只能进入 同一个队列(Queue),并且由 一个消费者 进行顺序消费。
- 优点:保证全局严格顺序。
- 缺点:吞吐量受限,单个消费者成为性能瓶颈。
分区顺序(Partition Order)
- 不同业务标识(如订单 ID) 绑定到特定 消息队列(Queue),同一个 Queue 内保证顺序,但不同 Queue 可并行消费。
- 优点:既保证局部顺序,又能提高吞吐量。
- 缺点:如果一个 Queue 处理变慢,可能会影响该 Queue 内的所有消息。
分区消费实现
生产者在sendMessage时,自定义一个MessageQueueSelector消息队列选择器,在里面手动选择queue,例如可以:
1 | SendResult sendResult = producer.send(msg, (mqs, msg1, arg) -> { |
消息发送的可靠性
RocketMQ 提供了几种不同的 发送消息的模式,可以保证消息是否成功发送到 Broker。
同步发送(Sync)
- 同步发送是最常见的方式,生产者发送消息后,会 阻塞等待 Broker 的返回结果,直到收到确认信息。同步发送能确保消息确实到达 Broker,并提供准确的发送结果。
- 同步发送在发送失败时,通常会依赖于 重试机制。RocketMQ 提供了自动重试的功能,在发生发送失败时,它会 自动重试 多次,直到达到最大重试次数,或者成功发送消息。
异步发送(Async)
- 异步发送是另一种方式,生产者发送消息后不会等待返回结果,而是 通过回调函数 处理发送结果。虽然不阻塞,但可能会因为消息发送失败而需要在回调中处理。
- 异步发送的失败需要在回调函数中显式处理,RocketMQ 不会自动重试。
一次消息发送(Oneway)
- 在这种模式下,生产者 不等待任何确认,直接将消息发送到 Broker。适用于 不关心消息是否成功发送 的场景。
消息保存的可靠性
- 异步/同步刷盘
- 异步/同步主从复制
消息接收的可靠性
取消自动,消费完成后手动ack
延迟重试(每次延迟增大)
多次重试失败后进入死信队列,手动处理或定时任务处理
幂等
redis id / 数据库id