戰(zhàn):SBOM驅(qū)動(dòng)的持續(xù)依賴漏洞監(jiān)控)
半年多以前掃過(guò)一遍依賴報(bào)告歸檔在某個(gè)共享盤(pán)里今天某個(gè)公共組件爆出高危漏洞團(tuán)隊(duì)第一反應(yīng)是翻報(bào)告——可那份報(bào)告只能說(shuō)明當(dāng)時(shí)沒(méi)問(wèn)題。這就是絕大多數(shù)依賴掃描的真實(shí)處境掃描動(dòng)作完成了風(fēng)險(xiǎn)監(jiān)控卻沒(méi)建立起來(lái)。Dependency-Track 解決的正是這件事它不是又一個(gè)掃描器而是一個(gè)持續(xù)運(yùn)轉(zhuǎn)的軟件成分賬本把每個(gè)項(xiàng)目用到的組件版本登記在案然后反復(fù)拿這些版本去比對(duì)最新公開(kāi)的漏洞情報(bào)一旦某個(gè)老項(xiàng)目里躺著的依賴被披露有問(wèn)題你能立刻定位到底是哪幾個(gè)項(xiàng)目、哪個(gè)版本、被誰(shuí)引用。本文按從部署到跑通再到日常使用的完整路徑展開(kāi)適合已經(jīng)接觸過(guò) SBOM 概念、想把依賴治理從季度巡檢變成實(shí)時(shí)盯防的開(kāi)發(fā)、測(cè)試和安全的同學(xué)也適合完全沒(méi)部署過(guò)這類平臺(tái)、想先跑一套環(huán)境看看效果的新手。1. 從掃一次就歸檔到天天盯著看它到底補(bǔ)的是哪塊短板1.1 一次性掃描方案留下的三個(gè)斷點(diǎn)絕大多數(shù)團(tuán)隊(duì)最早接觸的依賴檢查都是集成在構(gòu)建流程里的那一類構(gòu)建時(shí)跑一遍發(fā)現(xiàn)問(wèn)題就阻斷不阻斷就出份報(bào)告。這個(gè)模式聽(tīng)著很嚴(yán)實(shí)際跑下來(lái)有三個(gè)明顯的斷點(diǎn)。第一個(gè)斷點(diǎn)是信息只在構(gòu)建那一刻有效。今天的構(gòu)建用的是 Spring 的某個(gè)版本構(gòu)建通過(guò)、報(bào)告清零、大家心情舒暢三個(gè)月后這個(gè)版本被披露存在反序列化風(fēng)險(xiǎn)但沒(méi)有任何機(jī)制會(huì)主動(dòng)回來(lái)告訴你你線上跑的那個(gè)服務(wù)還掛在這個(gè)版本上。漏洞披露是持續(xù)發(fā)生的掃描卻是一次性的兩邊的時(shí)間軸對(duì)不上。第二個(gè)斷點(diǎn)是掃描結(jié)果散落在各個(gè)流水線的構(gòu)建產(chǎn)物里。十個(gè)項(xiàng)目十條流水線每份報(bào)告格式不一樣、存放位置不一樣、保留策略也不一樣。真要回答這個(gè)漏洞影響我們哪些服務(wù)得挨個(gè)翻翻完還得自己人工比對(duì)版本號(hào)效率極低而且極容易漏。第三個(gè)斷點(diǎn)是版本號(hào)的口徑不統(tǒng)一。構(gòu)建工具里寫(xiě)的是1.2.3傳遞依賴實(shí)際引進(jìn)來(lái)的是1.2.4有的包管理器還會(huì)把版本寫(xiě)成范圍表達(dá)式。人工看依賴樹(shù)時(shí)很容易把聲明版本當(dāng)成實(shí)際版本最后得出的結(jié)論是錯(cuò)位的。Dependency-Track 的定位就是把這些斷點(diǎn)一次性補(bǔ)上。它自己不做代碼掃描不分析你的源碼只做一件事接收一份標(biāo)準(zhǔn)化的組件清單長(zhǎng)期保存持續(xù)比對(duì)漏洞情報(bào)并在狀態(tài)變化時(shí)通知你。1.2 SBOM 是賬本漏洞庫(kù)是流水平臺(tái)負(fù)責(zé)對(duì)賬理解這個(gè)工具抓住兩個(gè)概念就夠了。SBOM軟件物料清單本質(zhì)就是你項(xiàng)目的一份配料表。它按標(biāo)準(zhǔn)格式描述了這個(gè)應(yīng)用里包含哪些組件、每個(gè)組件什么版本、由誰(shuí)發(fā)布、通過(guò)什么方式引入。目前流通最廣的兩種格式是 CycloneDX 和 SPDX前者在安全場(chǎng)景下用得更多生態(tài)也更全主流構(gòu)建工具基本都有對(duì)應(yīng)的插件能直接吐出來(lái)。漏洞情報(bào)源就是持續(xù)更新的問(wèn)題公告。平臺(tái)內(nèi)置了幾個(gè)分析器會(huì)定期把公開(kāi)漏洞庫(kù)里的條目拉下來(lái)建立本地索引然后用 SBOM 里的每個(gè)組件去索引里查。平臺(tái)的價(jià)值就體現(xiàn)在對(duì)賬這個(gè)動(dòng)作可以無(wú)限次重復(fù)。你把 SBOM 傳上去之后它不會(huì)像掃描器那樣跑完就出結(jié)論而是會(huì)拿著這份清單里的每個(gè)組件反復(fù)去查最新情報(bào)。今天沒(méi)問(wèn)題的組件下個(gè)月可能就有問(wèn)題了平臺(tái)會(huì)自動(dòng)把狀態(tài)從無(wú)風(fēng)險(xiǎn)改成有風(fēng)險(xiǎn)并按照你配的規(guī)則推送出來(lái)。這個(gè)機(jī)制聽(tīng)起來(lái)簡(jiǎn)單卻是整個(gè)依賴治理鏈條里最難靠人工維持的一環(huán)——因?yàn)樗蟮氖敲刻於甲鲆槐槎俗霾坏綑C(jī)器可以。需要注意的一點(diǎn)平臺(tái)的風(fēng)險(xiǎn)判斷完全依賴公開(kāi)情報(bào)的時(shí)效性和準(zhǔn)確性。它告訴你這個(gè)版本被披露有某某問(wèn)題不代表你的代碼真的調(diào)用了那段有問(wèn)題的邏輯實(shí)際可利用性還得結(jié)合調(diào)用路徑自己判斷。2. 部署前先想清楚組件構(gòu)成、資源盤(pán)子與數(shù)據(jù)庫(kù)選型2.1 三個(gè)進(jìn)程各管什么端口怎么分Dependency-Track 的服務(wù)端不是單體標(biāo)準(zhǔn)部署下要跑兩個(gè) Java 進(jìn)程加一個(gè)數(shù)據(jù)庫(kù)。API 服務(wù)端是核心負(fù)責(zé)接收 SBOM、跑分析器、管理項(xiàng)目與策略、對(duì)外提供 REST 接口默認(rèn)監(jiān)聽(tīng) 8080。前端服務(wù)端是一個(gè)獨(dú)立部署的靜態(tài)資源加反向代理容器默認(rèn)也監(jiān)聽(tīng) 8080它只負(fù)責(zé)渲染界面所有數(shù)據(jù)交互都是瀏覽器直接打到 API 服務(wù)端。數(shù)據(jù)庫(kù)存項(xiàng)目、組件、漏洞關(guān)聯(lián)、策略這些結(jié)構(gòu)化數(shù)據(jù)。這里有個(gè)新手特別容易栽的坑兩個(gè)容器默認(rèn)端口都是 8080如果直接映射到宿主機(jī)又不改端口必然沖突。我一般會(huì)這樣規(guī)劃API 服務(wù)端映射到宿主機(jī) 8081前端映射到 8080瀏覽器訪問(wèn) 8080 上的界面。同時(shí)前端的API_BASE_URL環(huán)境變量必須指向?yàn)g覽器能訪問(wèn)到的 API 地址而不是容器內(nèi)部的localhost。寫(xiě)成http://localhost:8081只在你本機(jī)瀏覽器直接訪問(wèn)時(shí)有效如果部署在服務(wù)器上就得寫(xiě)成服務(wù)器 IP 或域名否則界面能打開(kāi)但所有請(qǐng)求都 404讓人一臉懵。2.2 數(shù)據(jù)庫(kù)選型演示用內(nèi)嵌長(zhǎng)期用外部API 服務(wù)端支持兩種數(shù)據(jù)庫(kù)模式。一種是內(nèi)嵌的嵌入式數(shù)據(jù)庫(kù)零配置容器一啟動(dòng)就能用適合本地評(píng)估、寫(xiě) Demo、臨時(shí)驗(yàn)證。另一種是外部的 PostgreSQL適合任何要長(zhǎng)期跑的場(chǎng)景。我強(qiáng)烈建議只要不是純本地試驗(yàn)一律上 PostgreSQL。原因很實(shí)際內(nèi)嵌模式的數(shù)據(jù)文件掛載在容器卷里一旦容器重建、卷沒(méi)掛對(duì)、或者升級(jí)時(shí)鏡像行為變化數(shù)據(jù)說(shuō)沒(méi)就沒(méi)了而且內(nèi)嵌模式在并發(fā)寫(xiě)入和大量組件索引時(shí)的表現(xiàn)明顯吃力項(xiàng)目數(shù)量上去之后查詢會(huì)變得很慢。PostgreSQL 則是成熟方案?jìng)浞?、遷移、擴(kuò)容都有現(xiàn)成手段。資源方面給一個(gè)參考盤(pán)子項(xiàng)目數(shù)在 50 個(gè)以內(nèi)、每個(gè) SBOM 幾百個(gè)組件API 服務(wù)端 2 核 4G、PostgreSQL 2 核 2G 基本夠用如果要長(zhǎng)期保留完整的漏洞庫(kù)索引API 服務(wù)端內(nèi)存建議給到 4G 以上因?yàn)槁┒磶?kù)全量索引加載起來(lái)體積不小內(nèi)存不足會(huì)導(dǎo)致頻繁 GC表現(xiàn)為界面越用越卡。磁盤(pán)上留出至少 20G 給數(shù)據(jù)庫(kù)漏洞庫(kù)索引和組件記錄都會(huì)持續(xù)增長(zhǎng)。部署時(shí)區(qū)也要留意。容器默認(rèn)時(shí)區(qū)往往是 UTC導(dǎo)致界面上的時(shí)間戳和你本地差 8 小時(shí)排查到底什么時(shí)候同步的時(shí)很容易誤判。建議給容器統(tǒng)一掛上時(shí)區(qū)環(huán)境變量。3. 把服務(wù)拉起來(lái)一份能直接復(fù)用的編排配置3.1 編排文件逐段拆解下面這份配置是我在測(cè)試環(huán)境反復(fù)用過(guò)的基礎(chǔ)版本三個(gè)服務(wù)、數(shù)據(jù)卷持久化、端口錯(cuò)開(kāi)拿來(lái)改改就能用。version: 3.8 services: dtrack-apiserver: image: dependencytrack/apiserver:4.11.3 container_name: dtrack-apiserver environment: - ALPINE_DATABASE_MODEexternal - ALPINE_DATABASE_URLjdbc:postgresql://dtrack-postgres:5432/dtrack - ALPINE_DATABASE_DRIVERorg.postgresql.Driver - ALPINE_DATABASE_USERNAMEdtrack - ALPINE_DATABASE_PASSWORDchange_me_strong - ALPINE_CORS_ENABLEDtrue - ALPINE_CORS_ALLOW_ORIGINhttp://192.168.10.20:8080 - TZAsia/Shanghai volumes: - ./dtrack-data:/data ports: - 8081:8080 depends_on: - dtrack-postgres restart: unless-stopped dtrack-frontend: image: dependencytrack/frontend:4.11.3 container_name: dtrack-frontend environment: - API_BASE_URLhttp://192.168.10.20:8081 - TZAsia/Shanghai ports: - 8080:8080 depends_on: - dtrack-apiserver restart: unless-stopped dtrack-postgres: image: postgres:15-alpine container_name: dtrack-postgres environment: - POSTGRES_USERdtrack - POSTGRES_PASSWORDchange_me_strong - POSTGRES_DBdtrack - TZAsia/Shanghai volumes: - ./pgdata:/var/lib/postgresql/data restart: unless-stopped幾處值得單獨(dú)說(shuō)明的地方。ALPINE_CORS_ALLOW_ORIGIN必須填前端的完整訪問(wèn)地址包括協(xié)議、IP、端口一個(gè)字符都不能差。這是瀏覽器同源策略的要求填錯(cuò)的表現(xiàn)是登錄頁(yè)面能刷出來(lái)但點(diǎn)登錄按鈕一直轉(zhuǎn)圈瀏覽器控制臺(tái)里全是跨域報(bào)錯(cuò)。我第一次部署時(shí)就因?yàn)檫@個(gè)卡了半小時(shí)一直以為是后端沒(méi)啟動(dòng)。./dtrack-data:/data這個(gè)掛載是 API 服務(wù)端的持久化目錄里面會(huì)存配置文件和臨時(shí)數(shù)據(jù)。注意宿主機(jī)目錄的屬主權(quán)限容器內(nèi)進(jìn)程以非 root 用戶運(yùn)行如果目錄屬主是 root 且權(quán)限是 755寫(xiě)不進(jìn)去日志里會(huì)報(bào)權(quán)限拒絕。穩(wěn)妥做法是提前創(chuàng)建目錄并放開(kāi)寫(xiě)權(quán)限。鏡像版本號(hào)建議鎖定具體小版本別用latest。這個(gè)項(xiàng)目的版本迭代節(jié)奏不慢跨大版本升級(jí)時(shí)數(shù)據(jù)庫(kù)表結(jié)構(gòu)可能有變化用latest意味著某次重啟就悄悄換了版本出問(wèn)題時(shí)排查方向會(huì)被帶偏。3.2 首次啟動(dòng)與初始賬號(hào)處理編排文件就位后docker compose up -d拉起然后用docker compose logs -f dtrack-apiserver盯日志。啟動(dòng)時(shí)間取決于機(jī)器性能通常一兩分鐘能起來(lái)看到類似server started的字樣就說(shuō)明 API 服務(wù)端就緒了。數(shù)據(jù)表結(jié)構(gòu)會(huì)在首次啟動(dòng)時(shí)自動(dòng)初始化這個(gè)過(guò)程也需要時(shí)間期間前端訪問(wèn)會(huì)報(bào)各種奇怪的 500屬正?,F(xiàn)象耐心等。初始賬號(hào)是內(nèi)置的 admin密碼同樣有默認(rèn)值。各版本的具體默認(rèn)口令請(qǐng)?jiān)趩?dòng)完成后查閱對(duì)應(yīng)版本的官方部署說(shuō)明不要憑記憶輸。登錄后第一件事就是改密碼第二件事是創(chuàng)建一個(gè)專用的 API 賬號(hào)。這個(gè)平臺(tái)的角色權(quán)限設(shè)計(jì)得比較細(xì)日常給流水線用的是只有上傳 SBOM 權(quán)限的賬號(hào)不要圖省事直接拿 admin 的密鑰往 CI 里塞。創(chuàng)建 API 密鑰的路徑在 管理 → 訪問(wèn)管理 → 團(tuán)隊(duì)新建團(tuán)隊(duì)、分配權(quán)限、生成密鑰密鑰只在生成時(shí)顯示一次記得當(dāng)場(chǎng)記下來(lái)。如果你是升級(jí)而不是新建務(wù)必先備份數(shù)據(jù)庫(kù)再確認(rèn)目標(biāo)版本的升級(jí)說(shuō)明里有沒(méi)要求先跑某次中間版本??绱蟀姹局苯犹苋菀卓ㄔ诒斫Y(jié)構(gòu)遷移上。4. 接上情報(bào)源漏洞分析器怎么配才不白跑4.1 漏洞庫(kù)同步配額、節(jié)奏與首次全量的代價(jià)平臺(tái)跑起來(lái)不等于能查漏洞還要把情報(bào)源接上。主力的公開(kāi)漏洞庫(kù)需要申請(qǐng)一個(gè)訪問(wèn)密鑰免費(fèi)申請(qǐng)但有速率限制。這個(gè)限制是雙刃劍不填密鑰也能用只是拉取速度極慢全量同步可能要跑很久填了密鑰速度明顯提升但短時(shí)間內(nèi)頻繁請(qǐng)求會(huì)被限流返回 403 甚至臨時(shí)封禁。配置入口在 管理 → 分析器 → 對(duì)應(yīng)的漏洞庫(kù)分析器填密鑰、勾選啟用。保存之后平臺(tái)不會(huì)立刻開(kāi)始拉需要手動(dòng)觸發(fā)一次全量同步或者等定時(shí)任務(wù)。首次全量同步是最考驗(yàn)?zāi)托牡囊徊轿乙?jiàn)過(guò)跑了三四個(gè)小時(shí)還沒(méi)結(jié)束的。原因有兩個(gè)一是數(shù)據(jù)量本身就大二十多萬(wàn)條漏洞記錄加上關(guān)聯(lián)的組件標(biāo)識(shí)二是每拉一批要落庫(kù)建立索引。這個(gè)過(guò)程中可以隨時(shí)在界面上看同步狀態(tài)別重啟容器重啟會(huì)中斷并可能導(dǎo)致部分?jǐn)?shù)據(jù)重復(fù)處理。同步完成后還有個(gè)容易忽略的點(diǎn)增量更新的頻率。默認(rèn)配置下是每天跑一次增量這個(gè)頻率對(duì)絕大多數(shù)團(tuán)隊(duì)夠用。如果你們對(duì)新生漏洞的響應(yīng)要求很高可以調(diào)高頻率但要權(quán)衡水源的限流風(fēng)險(xiǎn)別把自己搞得被拒絕訪問(wèn)。4.2 分析器各管一攤別指望一個(gè)包打天下平臺(tái)內(nèi)置的分析器不止一個(gè)分工不同。漏洞庫(kù)分析器負(fù)責(zé)比對(duì) CVE 類公開(kāi)漏洞是主力。另一個(gè)常見(jiàn)的分析器基于第三方組件情報(bào)服務(wù)除了漏洞還會(huì)給出組件的維護(hù)狀態(tài)、是否有新版本等信息需要單獨(dú)注冊(cè)賬號(hào)同樣有免費(fèi)額度限制。此外還有一些針對(duì)特定生態(tài)的分析器比如檢查組件是否已被官方廢棄、檢查許可證合規(guī)等。我踩過(guò)的一個(gè)坑是只開(kāi)了漏洞庫(kù)分析器就一直以為組件生命周期這類信息也會(huì)自動(dòng)出現(xiàn)結(jié)果界面上那幾列一直是空的。后來(lái)才明白不同的分析器要在 分析器 頁(yè)面逐個(gè)啟用而且有一些需要額外憑據(jù)。另一個(gè)細(xì)節(jié)是分析器啟用后對(duì)已有數(shù)據(jù)的影響。新啟用一個(gè)分析器它不會(huì)自動(dòng)回掃所有歷史項(xiàng)目需要手動(dòng)觸發(fā)重新分析。項(xiàng)目數(shù)量多的話這一步耗時(shí)也不短。建議在部署初期就把要用的分析器一次性配齊避免后面反復(fù)補(bǔ)掃。5. 把依賴送進(jìn)去SBOM 從哪來(lái)、怎么傳5.1 生成清單先用構(gòu)建工具自帶的插件最省事的方式是用各語(yǔ)言生態(tài)現(xiàn)成的插件在打包階段順手生成。Java 項(xiàng)目加一個(gè) Maven 插件綁定到package階段構(gòu)建完target目錄下就多出一份 CycloneDX 格式的 JSONNode 項(xiàng)目有對(duì)應(yīng)的命令行工具npx直接跑容器鏡像可以用鏡像掃描工具以 CycloneDX 格式輸出。Go 和 Python 也都有成熟插件。有個(gè)細(xì)節(jié)值得強(qiáng)調(diào)清單要包含傳遞依賴否則等于白做。很多團(tuán)隊(duì)的依賴問(wèn)題不在自己直接聲明的那幾個(gè)包上而在第三層、第四層被間接引進(jìn)來(lái)的包。生成時(shí)確認(rèn)工具的參數(shù)里包含了完整依賴樹(shù)的解析而不是只讀一下清單文件。生成出來(lái)的文件我習(xí)慣先人眼看一眼重點(diǎn)看三個(gè)地方metadata.component里的項(xiàng)目名和版本對(duì)不對(duì)、components數(shù)組的長(zhǎng)度是不是符合預(yù)期、每個(gè)組件有沒(méi)有purl字段。purl是包的唯一標(biāo)識(shí)形如pkg:maven/group/artifact1.2.3平臺(tái)靠它來(lái)精確匹配漏洞記錄。缺了purl的組件匹配率會(huì)直線下降等于擺在那里但查不出問(wèn)題。5.2 走 API 上傳一條 curl 打通全流程界面上的上傳 BOM按鈕適合手工驗(yàn)證真正要用起來(lái)必須走接口。核心接口是PUT /api/v1/bom用 multipart 表單提交。export DT_URLhttp://192.168.10.20:8081 export DT_API_KEY你的密鑰 export PROJECT_UUID項(xiàng)目的唯一標(biāo)識(shí) curl -sS -X PUT ${DT_URL}/api/v1/bom \ -H X-Api-Key: ${DT_API_KEY} \ -H Accept: application/json \ -F project${PROJECT_UUID} \ -F autoCreatetrue \ -F bomtarget/bom.json幾個(gè)參數(shù)的實(shí)際含義得說(shuō)清楚。project傳的是項(xiàng)目 UUID不是項(xiàng)目名這個(gè)值在項(xiàng)目詳情頁(yè)的地址欄里能看到。autoCreatetrue表示如果這個(gè) UUID 對(duì)應(yīng)的項(xiàng)目不存在就自動(dòng)創(chuàng)建配合項(xiàng)目名和版本可以在某些接口變體里一并指定。bom是文件字段。上傳接口是異步的返回 200 不代表處理完了只代表收到并入了隊(duì)列。真正的處理結(jié)果要看響應(yīng)里的 token再拿 token 去查處理狀態(tài)或者在界面上看項(xiàng)目的BOM 處理頁(yè)面。我第一次接入時(shí)看到 200 就以為成功了結(jié)果刷新項(xiàng)目一看組件數(shù)是 0回頭查才發(fā)現(xiàn)處理失敗了——原因是文件格式不合法。autoCreate這個(gè)開(kāi)關(guān)是把雙刃劍。開(kāi)著的確省事但一旦項(xiàng)目名或版本算錯(cuò)會(huì)在平臺(tái)里悄悄造出一堆名字相近的重復(fù)項(xiàng)目清理起來(lái)很煩。我的做法是流水線里首次上傳前先用接口查一下項(xiàng)目是否存在不存在就顯式創(chuàng)建并把 UUID 落下來(lái)寫(xiě)進(jìn)流水線變量之后固定用這個(gè) UUID 上傳autoCreate只在本地試驗(yàn)時(shí)開(kāi)。5.3 接進(jìn)流水線位置比工具更重要SBOM 上傳放在流水線的哪一步直接影響數(shù)據(jù)的可用性。放在構(gòu)建成功之后、部署之前是最合理的。此時(shí)依賴已經(jīng)解析完成清單是準(zhǔn)確的如果上傳失敗可以讓流水線繼續(xù)因?yàn)樯蟼魇〔辉撟钄鄻I(yè)務(wù)發(fā)布但要在日志里明確告警出來(lái)。有一個(gè)取舍要提前想是按每次構(gòu)建都上傳還是只在主干分支上傳我的經(jīng)驗(yàn)是主干分支每次構(gòu)建都傳特性分支只在需要評(píng)審時(shí)手動(dòng)傳。原因是平臺(tái)里每個(gè)項(xiàng)目對(duì)應(yīng)一個(gè) UUID如果每個(gè)特性分支都建一個(gè)項(xiàng)目項(xiàng)目列表會(huì)爆炸而且這些短期分支的漏洞狀態(tài)沒(méi)人看。主干持續(xù)覆蓋上傳的好處是同一個(gè)項(xiàng)目的組件清單始終反映最新?tīng)顟B(tài)漏洞關(guān)聯(lián)會(huì)自動(dòng)跟著更新。還有一個(gè)容易忽略的配置項(xiàng)目的版本維度。同一個(gè)服務(wù)有 1.0、2.1 多個(gè)在跑是建一個(gè)項(xiàng)目還是按版本建多個(gè)平臺(tái)的模型是項(xiàng)目 版本一個(gè)項(xiàng)目下可以有多個(gè)版本。實(shí)踐中我把服務(wù)作為項(xiàng)目把發(fā)布版本作為版本這樣既能看某個(gè)版本的具體情況也能在項(xiàng)目層面看到整體風(fēng)險(xiǎn)分布。6. 從發(fā)現(xiàn)到有人管策略規(guī)則和通知怎么配6.1 策略粒度別一上來(lái)就全量告警平臺(tái)默認(rèn)有一套策略比如發(fā)現(xiàn)某個(gè)嚴(yán)重級(jí)別以上的漏洞就標(biāo)記違規(guī)。這套默認(rèn)規(guī)則在項(xiàng)目少的時(shí)候還行項(xiàng)目一多就是災(zāi)難——幾千個(gè)組件里總有那么幾十個(gè)帶著各種歷史遺留的中低危問(wèn)題通知渠道天天響響到最后所有人都麻木了真出高危反而沒(méi)人看。我的建議是按影響面 可利用性分層建策略而不是按嚴(yán)重級(jí)別一刀切。策略維度規(guī)則示例違規(guī)動(dòng)作適用場(chǎng)景嚴(yán)重級(jí)別硬門(mén)檻嚴(yán)重級(jí)別為最高檔且組件為直接依賴標(biāo)記為拒絕對(duì)外服務(wù)項(xiàng)目版本狀態(tài)存在已修復(fù)的新版本且當(dāng)前版本落后兩個(gè)大版本標(biāo)記為需關(guān)注全部項(xiàng)目許可證約束命中特定許可證清單標(biāo)記為需關(guān)注對(duì)外分發(fā)的產(chǎn)品組件活躍度組件所屬項(xiàng)目已停止維護(hù)超過(guò)一定時(shí)間標(biāo)記為需關(guān)注核心系統(tǒng)排除規(guī)則明確判定為誤報(bào)的組件版本對(duì)忽略全部項(xiàng)目忽略這個(gè)能力一定要善用。有些漏洞在特定使用方式下根本不可利用比如某個(gè)庫(kù)的問(wèn)題出在一個(gè)你完全沒(méi)調(diào)用的功能模塊里。這種反復(fù)被報(bào)出來(lái)、團(tuán)隊(duì)已經(jīng)確認(rèn)無(wú)風(fēng)險(xiǎn)的條目就應(yīng)該進(jìn)白名單否則會(huì)持續(xù)污染告警信噪比。白名單要寫(xiě)明原因和確認(rèn)人別讓它變成一鍵消音。6.2 通知渠道分層推送控制噪音通知配置的模型是規(guī)則 發(fā)布器 模板。規(guī)則定義什么條件下觸發(fā)發(fā)布器定義往哪發(fā)模板定義消息長(zhǎng)什么樣。渠道選擇上我最推薦接團(tuán)隊(duì)日常用的即時(shí)通訊工具的 Webhook因?yàn)橄⑦M(jìn)了大群相關(guān)的人都能看到不容易被郵件規(guī)則過(guò)濾掉。郵件適合作為周報(bào)匯總不適合實(shí)時(shí)告警。推送分級(jí)很有必要。最高級(jí)別的違規(guī)直接推到主頻道并 相關(guān)值班人中等級(jí)別推到單獨(dú)的告警頻道每天匯總一次全部違規(guī)的完整清單做成定時(shí)報(bào)表一周發(fā)一次。這樣既保證高危能被立刻看到又不會(huì)讓日常討論被大量噪音淹沒(méi)。模板內(nèi)容要定制默認(rèn)模板信息太少。我一般會(huì)往里面加三樣?xùn)|西項(xiàng)目名和版本、組件的完整坐標(biāo)和當(dāng)前版本、以及平臺(tái)里該項(xiàng)目的直達(dá)鏈接。加了鏈接之后收到告警的人點(diǎn)一下就能看到上下文響應(yīng)速度會(huì)明顯快很多。通知觸發(fā)是有節(jié)流的同一組件的同一狀態(tài)不會(huì)反復(fù)推。但如果你頻繁重新上傳 SBOM可能會(huì)看到狀態(tài)在違規(guī)和正常之間來(lái)回跳導(dǎo)致通知重復(fù)。根源往往是不同流水線傳了不同版本的清單互相覆蓋排查時(shí)先看最近幾次上傳的時(shí)間戳。7. 跑起來(lái)之后才會(huì)遇到的問(wèn)題幾個(gè)真實(shí)的排查記錄7.1 組件明明在清單里為什么漏洞查不出來(lái)這是被問(wèn)得最多的一類問(wèn)題表現(xiàn)是 SBOM 上傳成功、組件數(shù)量也對(duì)但某個(gè)已知有漏洞的組件就是沒(méi)有關(guān)聯(lián)出漏洞。排查下來(lái)原因基本集中在三種。第一種purl缺失或格式不規(guī)范。平臺(tái)匹配漏洞靠的是包的生態(tài)、命名空間、名稱、版本這四個(gè)要素任何一個(gè)不對(duì)就匹配不上。有些生成工具默認(rèn)不輸出purl或者把 Maven 的 group 和 artifact 拼錯(cuò)了位置看起來(lái)很相似但匹配失敗。驗(yàn)證方法很簡(jiǎn)單進(jìn)組件詳情看標(biāo)識(shí)符字段是不是空的。第二種版本號(hào)被規(guī)范化了。有類包的版本里帶前導(dǎo) v、帶額外的元數(shù)據(jù)后綴、或者是范圍表達(dá)式生成工具解析后寫(xiě)進(jìn)清單的可能是規(guī)范化之后的形態(tài)而漏洞庫(kù)里的版本區(qū)間是按原始形態(tài)描述的。這類問(wèn)題比較隱蔽需要在組件詳情里對(duì)照實(shí)際聲明版本看一眼。第三種漏洞庫(kù)還沒(méi)同步到這條記錄。公開(kāi)漏洞從披露到進(jìn)入各個(gè)漏洞庫(kù)有延遲短的幾小時(shí)長(zhǎng)的幾天。如果漏洞是昨天剛公布的查不出來(lái)很正常。這時(shí)候可以看漏洞庫(kù)里該條目的最后修改時(shí)間來(lái)判斷。排查順序我一般這么走先看組件標(biāo)識(shí)符有沒(méi)有、對(duì)不對(duì)再看這個(gè)組件在平臺(tái)里的分析面板看分析器有沒(méi)有跑過(guò)、有沒(méi)有報(bào)錯(cuò)最后才是懷疑漏洞庫(kù)數(shù)據(jù)的時(shí)效。7.2 內(nèi)存與同步那點(diǎn)事幾個(gè)能省下大量時(shí)間的配置跑一段時(shí)間后最常見(jiàn)的抱怨是卡。界面加載慢、項(xiàng)目列表轉(zhuǎn)圈、分析任務(wù)排隊(duì)。第一個(gè)抓手是API 服務(wù)端的內(nèi)存。這個(gè)服務(wù)的默認(rèn)堆內(nèi)存偏保守項(xiàng)目多、組件多的時(shí)候會(huì)頻繁 GC。給容器配置合理的內(nèi)存上限并通過(guò)環(huán)境變量設(shè)置 JVM 堆大小通常調(diào)到 4G 以上會(huì)有明顯改善。但別把容器內(nèi)存限制和堆大小設(shè)成一樣大留出四分之一的余量給堆外內(nèi)存和系統(tǒng)否則容器會(huì)被 OOM 直接殺掉比卡更麻煩。第二個(gè)抓手是數(shù)據(jù)庫(kù)連接池。默認(rèn)連接數(shù)在小規(guī)模下夠用并發(fā)上傳多個(gè) SBOM 時(shí)會(huì)看到連接等待超時(shí)。適當(dāng)調(diào)大同時(shí)確認(rèn) PostgreSQL 的最大連接數(shù)配置跟得上兩頭不匹配一樣會(huì)報(bào)錯(cuò)。第三個(gè)抓手是分析任務(wù)的并發(fā)度。組件數(shù)量多的項(xiàng)目重新分析是個(gè)重活。并發(fā)開(kāi)太高會(huì)把 CPU 和數(shù)據(jù)庫(kù)打滿開(kāi)太低一次分析跑半小時(shí)。我的經(jīng)驗(yàn)值是并發(fā)度設(shè)在 CPU 核數(shù)左右比較平衡超過(guò)這個(gè)數(shù)收益不明顯。第四個(gè)是老項(xiàng)目的清理。跑了一年之后平臺(tái)里往往積累了大量已經(jīng)下線的服務(wù)和臨時(shí)項(xiàng)目這些項(xiàng)目的組件記錄還在被持續(xù)分析、持續(xù)產(chǎn)生告警。定期把確認(rèn)下線的項(xiàng)目設(shè)為未激活能顯著減少后臺(tái)負(fù)擔(dān)和告警噪音。7.3 備份與升級(jí)別把賬本弄丟了平臺(tái)里存的東西有個(gè)特點(diǎn)重建成本極高。項(xiàng)目、歷史 SBOM、白名單里的判定結(jié)論、策略配置這些都是人工積累出來(lái)的容器可以重建這些丟了就沒(méi)了。備份策略分兩層。數(shù)據(jù)庫(kù)按天全量轉(zhuǎn)儲(chǔ)這層是根本配置文件目錄那個(gè)掛載到/data的卷定期打包里面有些運(yùn)行期生成的密鑰材料恢復(fù)時(shí)如果沒(méi)有已簽發(fā)的令牌會(huì)全部失效。升級(jí)時(shí)的動(dòng)作順序固定下來(lái)先停前端和 API 服務(wù)端轉(zhuǎn)儲(chǔ)數(shù)據(jù)庫(kù)拉取目標(biāo)版本鏡像啟動(dòng)觀察日志直到初始化完成最后驗(yàn)證一遍登錄、項(xiàng)目列表、上傳??绱蟀姹緯r(shí)同時(shí)要看升級(jí)說(shuō)明里關(guān)于數(shù)據(jù)庫(kù)遷移的注意事項(xiàng)有些版本要求先升到某個(gè)中間版本再繼續(xù)硬跳會(huì)卡住。Dependency-Track 這套東西剛上手時(shí)容易覺(jué)得步驟多但真正的門(mén)檻不在部署而在于把它嵌進(jìn)團(tuán)隊(duì)的日?!猄BOM 要穩(wěn)定地產(chǎn)生并上傳策略要貼著實(shí)際情況調(diào)告警要有人接。我在實(shí)際推進(jìn)時(shí)最有效的一個(gè)做法是先從兩三個(gè)核心服務(wù)開(kāi)始把從構(gòu)建到告警的完整鏈路跑順讓團(tuán)隊(duì)先看到上周披露的那個(gè)漏洞我們?cè)趦尚r(shí)內(nèi)定位到了受影響的三個(gè)服務(wù)這樣的實(shí)際效果再往其它項(xiàng)目鋪開(kāi)。反過(guò)來(lái)一上來(lái)就全量接入、全量告警大概率兩周之后所有人都會(huì)把它當(dāng)噪音源屏蔽掉。