
在軟體供應鏈安全架構中,建置來源證明(Attestation)有一個關鍵前提:套件必須由受控且透明的 CI 環境獨立建置,開發者絕不能在本機完成封裝後再手動上傳。
這項規矩背後的防禦邏輯十分直截了當。Provenance 聲明所背書的,是「這個二進位產物是由指定倉庫的特定 Commit,在 GitHub 的代管 Runner 上編譯產出」。一旦產物曾在工程師的本機端停留再上傳,這條信任鏈便直接中斷,數位簽署便無法證明檔案在出廠前是否曾遭竄改。
本文以 qpkg-template 的發布架構為基礎,將發布管線完整公開。從開發者推送版本標籤開始,到 GitHub Release 頁面產生安裝包、雜湊總和、映像檔鎖定檔、授權聲明與數位證明,全程由自動化管線接管,實現真正的零人工接觸發布。依 qpkg-template v0.1.1 處理,相關連結另外標明。

.github/workflows/build.yml(v0.1.2) 只有一個 workflow,兩個 job。
push main / pull_request / workflow_dispatch / push tag v*
│
▼
test ─ shellcheck
─ check-pins
─ lifecycle 測試(真的 docker)
─ new-app 測試
│ 全部通過才往下
▼
build ─ tag 對 QPKG_VER 檢查 ← 只在 tag
─ 在 runner 上裝 QDK
─ dos2unix
─ qbuild
─ make release-files(SHA256SUMS)
─ attest-build-provenance ← 只在 tag
─ upload-artifact ← 每次都有
─ softprops/action-gh-release ← 只在 tag
四種觸發條件跑的是同一條線,差別只在標示「只在 tag」的三步。push 到 main 與 pull request 會跑完測試與打包,產出的 .qpkg 掛在 workflow run 的 artifact 上,可以下載來裝到 NAS 試,但不會出現在 release 頁面。只有 v* 開頭的 tag 才會簽 attestation 並發 release。這樣每一個 commit 都有一份可安裝的產物可以驗證,release 頁面上卻只有經過決定的版本。
整條線很短。v0.1.2 的 tag 那一次,test job 跑了 31 秒,build job 跑了 37 秒,從 push tag 到 release 頁面出現檔案約一分十秒。
發布管線定義於單一工作流程中,切分為檢驗與打包兩項工作。所有觸發條件都執行相同的基礎流程,差異僅在於簽署與發布步驟。一般推送與合併請求會跑完整體測試並完成打包,將產出的安裝包掛載於工作流程的暫存成品區,供內部測試安裝,但絕不推播至發布頁面。只有符合版本格式的標籤推送,才會正式簽署並對外發布。
在權限配置上,管線嚴格落實最小權限原則。工作流程頂層全域宣告為唯讀權限,禁止未授權的寫入行為。負責執行腳本檢驗的工作完全不具備寫入權限,杜絕測試階段第三方容器越權存取的風險。唯有進入封裝與發布階段的工作,才會針對發布發行、OIDC 身分換發憑證以及寫入數位證明等必要動作,逐一宣告提升權限。
整條發布管線極度精簡,檢驗與打包各耗時三十餘秒,從推送標籤到公開發布頁面生成全套驗證檔案,整體流程約一分多鐘即可收斂完成。
檢驗階段設立了四道獨立閘門,分別對應嵌入式系統常見的語法陷阱、依賴漂移以及生命週期異常。
QNAP 作業系統底層採用 BusyBox 環境,僅提供標準 Shell 直譯器,並不支援常見的 Bash 擴充語法。檢驗步驟強制採用靜態分析工具,以嚴格的 POSIX 規則逐一掃描所有腳本、套件常式與測試替身。這項檢驗能在原始碼階段直接攔截非標準語法,避免使用者在 NAS 實際安裝時才因為直譯器不支援而引發例外中斷。
管線會調用腳本全面檢驗映像檔鎖定清單,嚴格要求每一列配置都必須包含不可變的 SHA-256 Digest,同時確認服務宣告的所有容器都有對應的鎖定紀錄。若開發團隊在調整設定時誤將映像檔改回浮動標籤,管線將在此刻強制中斷,避免未經審核的外部映像檔潛入發布流程。
為了確保套件在儲存裝置上的穩定性,檢驗環境直接在虛擬主機上啟動實際的 Docker 服務,並透過替身工具模擬儲存系統專屬的設定讀寫與事件紀錄指令。整個測試流程涵蓋首次啟動、狀態頁切換、帶設定重啟、版本升級語意、離線映像檔匯入容錯以及移除保留資料等完整情境,確保應用程式生命週期的每一次狀態流轉都符合預期。
針對離線與隔離網段部署,測試也涵蓋了手動匯入映像檔的特殊情境。當匯入的映像檔缺乏倉庫摘要時,系統會自動切換為相容啟動模式,標記為未驗證狀態並寫入系統事件警告,在保持服務可用性的同時兼顧合規稽核軌跡。
範本存在的目的在於快速衍生新專案。檢驗步驟會模擬全新的專案建立流程,執行更名腳本並驗證套件設定、宣告常式與第三方聲明檔案是否正確置換,確保從範本派生出的新應用程式在起步階段就具備完整的合規基礎。
通過檢驗後,管線進入封裝階段。在安裝建置工具時,管線特別導入了換行符號自動正規化作業,避免跨平台開發時引入的斷行字元導致嵌入式環境找不到直譯器。產出二進位檔案後,系統會將授權條款、第三方聲明與映像檔鎖定檔一併打包,統整計算雜湊清單,再由官方動作套件為產物簽發不可竄改的來源證明。
一條發布管線是否值得信賴,取決於它所引用的建置元件是否同樣受到不可變鎖定。管線在相依元件上落實了全面的固化原則。
| 引用項目 | 潛在安全問題 | 強化對策 |
|---|---|---|
| 建置工具套件(QDK) | 追蹤預設分支會導致不同時期的建置產物規格不一致 | 鎖定特定 Commit SHA,工作流程與容器建置共用參照常數 |
| 自動化工作流程動作 | 浮動大版本標籤可能遭上游作者非預期移動 | 全數釘選為四十碼 Commit SHA,以註解保留版本號 |
| 靜態檢查與基底映像檔 | 浮動版本與鎖定清單原則衝突 | 全面改採 SHA-256 Digest 進行不可變鎖定 |
針對建置工具的鎖定,管線放棄拉取整個倉庫,改以淺層抓取指定 Commit SHA,既確保每一次產出的結構完全一致,又能提升建置效率。同時,倉庫整合了相依性自動檢查機制,每週定期針對底層映像檔與動作元件掃描新版本,將外部版本演進轉化為可經由審查決定是否合併的提案,杜絕暗中自動升級帶來的供應鏈破口。
在自動化發布中,常見的作法是由系統自動讀取 Git Tag 並覆寫進設定檔中。這種方式看似便利,卻潛藏嚴重的完整性瑕疵:當系統在未提交變更的情況下修改檔案並直接封裝,會導致最終發布的二進位產物與 Commit 所記錄的原始碼出現偏差,連帶削弱了數位證明的真實性。
本架構選擇嚴格維護「單一真實來源」。版本號由工程師在設定檔中明確定義並納入版本控制,自動化流程僅負責核對。當推送的標籤名稱與設定檔版本不一致時,管線會在安裝建置工具前立即中止作業。寧可讓標籤錯誤導致發布失敗並重新修正,也絕不容許版本宣稱與實體產物脫鉤的檔案對外釋出。
- name: Tag must match QPKG_VER
if: startsWith(github.ref, 'refs/tags/v')
run: |
VER=$(sed -n 's/^QPKG_VER="\(.*\)"/\1/p' qpkg.cfg)
[ "v$VER" = "$GITHUB_REF_NAME" ] || {
echo "qpkg.cfg says $VER but tag is $GITHUB_REF_NAME" >&2
exit 1
}
這道檢查確保了發布頁面上的每一份檔案,其內部標籤都與 Git 歷史紀錄完全吻合。
完成發布後,終端用戶或資安稽核人員可以使用官方命令列工具驗證產物的合法性。透過比對雜湊清單確認檔案傳輸無誤後,再藉由來源驗證指令,比對二進位套件與版本標籤的數位簽章。
gh attestation verify MyApp_0.1.2_x86_64.qpkg --repo ivanusto/qpkg-template --source-ref refs/tags/v0.1.2
指令加上來源參照限制是必要的安全手段。由於 Attestation 綁定的是檔案雜湊值,若未限定標籤來源,不同版本間未變更的附屬檔案可能以其他版本的簽章通過檢驗。加入限制即可確認該產物確實出自該特定版本的發布事件。
在軟體供應鏈的最高標準中,除了來源證明外,下一步是追求「可重現建置」(Reproducible Builds)。目前的建置工具會在封裝過程中自動注入當下的打包時間戳記,造成同一份原始碼在不同時間編譯出的二進位檔案雜湊值有所出入。來源證明保證了產物來自特定環境與 Commit,而達到真正的位元組層級可重現,則需要在打包底層全面接管時間戳記與壓縮參數,這將是後續架構持續強化的重要方向。
這套自動化管線串聯了系統生命週期管理、容器依賴鎖定以及供應鏈安全簽署,完成了應用程式基礎設施的核心骨架。
第一段的骨架到今天完成,Day 2 生命週期、Day 3 版本鎖定、Day 4 CI。從下一篇開始,系列將進入實體應用的整合案例,深入探討如何將這套標準化範本套用至更具技術挑戰的服務上,示範如何在不更動核心框架的前提下,透過自訂鉤子達成硬體加速轉碼裝置直通與特定網路架構配置。
系列文章與程式碼索引:onprem-ops-30days
本日程式碼:qpkg-template @ v0.1.1、qpkg-template @ v0.1.2
參考資料
attestation 指令首次提供的版本