软件成分分析系统 **项目简介:**开发基于包管理器构建过程的软件成分分析系统,采用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 2 3 4 5 SendResult sendResult = producer.send(msg, (mqs, msg1, arg) -> { int orderId = (int ) arg; int queueIndex = orderId % mqs.size(); return mqs.get(queueIndex); }, orderId);
消息发送的可靠性 RocketMQ 提供了几种不同的 发送消息的模式 ,可以保证消息是否成功发送到 Broker。
同步发送(Sync)
同步发送是最常见的方式,生产者发送消息后,会 阻塞等待 Broker 的返回结果,直到收到确认信息。同步发送能确保消息确实到达 Broker,并提供准确的发送结果。
同步发送在发送失败时,通常会依赖于 重试机制 。RocketMQ 提供了自动重试的功能,在发生发送失败时,它会 自动重试 多次,直到达到最大重试次数,或者成功发送消息。
异步发送(Async)
异步发送是另一种方式,生产者发送消息后不会等待返回结果,而是 通过回调函数 处理发送结果。虽然不阻塞,但可能会因为消息发送失败而需要在回调中处理。
异步发送的失败需要在回调函数中显式处理,RocketMQ 不会自动重试。
一次消息发送(Oneway)
在这种模式下,生产者 不等待任何确认 ,直接将消息发送到 Broker。适用于 不关心消息是否成功发送 的场景。
消息保存的可靠性
消息接收的可靠性 取消自动,消费完成后手动ack
延迟重试(每次延迟增大)
多次重试失败后进入死信队列,手动处理或定时任务处理
幂等 redis id / 数据库id