💡 今日學習目標:學會在 GitHub Actions 部署自動化 CI/CD 流水線,讓每一次程式碼提交都自動執行 SAST 資安掃描,理解為什麼「掃描」這件事不能只靠工程師手動記得做。
GitHub 在 2025 年初公布了一個值得每個工程師停下來看一眼的數字:光是 2024 年一年,GitHub 的機密掃描(Secret Scanning)就偵測到超過 3,900 萬筆外洩機密(API 金鑰、密碼、Token)被推送進儲存庫。
📖 資料來源:GitHub Blog —《GitHub found 39M secret leaks in 2024. Here's what we're doing to help》
3,900 萬筆是什麼概念?一年有 525,600 分鐘,把它攤平下去是每分鐘 74 次,換句話說:平均每一秒鐘,就有超過一次的機密被推進了某個儲存庫。
你不可能雇用一支團隊,24 小時盯著每一次 git push,用肉眼去找哪一行不小心把 AWS_SECRET_KEY 寫死進程式碼裡。
Day 08,我們在自己的 IDE 裡裝上了 SonarQube for IDE,讓漏洞在打字的當下就被標記出來。但 IDE 級別的掃描有一個天生的限制:它只在「有裝、有開、有看」的那台電腦上生效。 只要有一個人忘記裝、關掉了提示,或是直接用網頁編輯器改了一行程式碼就送出 PR,IDE 那道防線就形同虛設。
今天,我們要把這道防線焊進沒有人能繞過去的地方:CI/CD 流水線本身。
你 Day 09 架的那台 SonarQube,GitHub Actions 連不到。
原因很單純:Actions 的執行器(Runner)跑在 GitHub 的雲端機房,而你那台在 http://localhost:9000,那個 localhost 指的是你自己的電腦。雲端的 Runner 連不到它,也永遠連不到。
如果你直接把 SONAR_HOST_URL 填成 http://localhost:9000,工作流程會照跑、然後卡住、最後逾時失敗,畫面上只會看到連線被拒絕。錯誤訊息不會告訴你「因為那是你家的電腦」。
三條路可以走:
| 做法 | 適合誰 | 代價 |
|---|---|---|
| ① SonarQube Cloud(本文採用) | 練習、公開儲存庫 | 公開儲存庫完全免費,不用架任何東西 |
| ② 把自架站對外開放 | 公司內已有正式伺服器 | 需要固定 IP/網域與 HTTPS |
| ③ 改用自架 Runner | 內網環境、不想對外開 | Runner 跑在你自己的機器上,就連得到 localhost |
本文走第 ① 條,因為它維持了這 30 天「一毛錢都不用花」的前提:到 sonarcloud.io 用 GitHub 帳號登入 ➔ 匯入你的公開儲存庫 ➔ 產生一組 Token,五分鐘就好。
🛑 如果你想選 ②,請先想清楚一件事。 那台機器上有你所有專案的原始碼分析結果,而 Day 09 我們只改過 admin 密碼,沒有做任何其他加固。把它直接開到公網,本身就是一個 Day 19 講的「安全設定缺陷」。 至少要加上 HTTPS、限制來源 IP,或者放在 VPN 後面再說。
SONAR_TOKEN,值就是剛剛在 SonarQube Cloud 產生的那組 Token。SONAR_HOST_URL 指向你伺服器的網址。用 SonarQube Cloud 的話不必設,Action 會自己找到正確位置。🔑 注意這裡用的是 Secrets,不是直接寫進 YAML。 Secrets 的值出現在 Actions 日誌時會被自動遮成
***,而寫進 YAML 的東西會跟著程式碼進版本庫,任何看得到這個倉庫的人都看得到。這就是 Day 08 那條「硬編碼機密」規則在 CI 環境的版本。
在專案中建立 .github/workflows/security-scan.yml:
name: Security & SAST Analysis
# 觸發條件:Push 至 main 分支,或有人對 main 開 Pull Request 時自動觸發
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs:
sonar_scan:
name: Run SonarQube Security Scan
runs-on: ubuntu-latest
steps:
# 1. 拉取專案程式碼
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0 # 抓取完整 Commit 歷史以精準分析增量修改
# 2. 呼叫官方 SonarQube Action 進行掃描
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v8
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# 用 SonarQube Cloud 的話,這一行不用設
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
# 3. 檢查 Quality Gate 結果,沒 Pass 就讓 CI Build 失敗
- name: SonarQube Quality Gate Check
uses: SonarSource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
存檔並推送這支 YAML,下一次你對 main 開 Pull Request 時,GitHub Actions 就會自動觸發整套掃描,不需要任何人手動下指令。
📌 用 SonarQube Cloud 的話,還缺一個東西:專案根目錄要有一支
sonar-project.properties,至少寫上這兩行,否則掃描會直接報錯說找不到 organization:sonar.organization=你的組織代號 sonar.projectKey=你的專案代號好消息是你不必自己寫:在 SonarQube Cloud 匯入儲存庫時,精靈會把這兩個值連同一份 workflow 範本一起產生給你,照它給的貼上就好。自架版則不需要
organization這一行。
⚠️ 請特別注意那兩個
@v8與@v1,不要照抄成@master。網路上大量範例寫的是
@master,意思是「每次執行都抓那個分支的最新程式碼」。這代表別人推一次 commit,你的 CI 下一次跑的東西就變了,而你不會收到任何通知。而 Action 是在你的環境裡執行、拿得到你所有 Secrets 的程式碼。這不是理論上的擔心。2025 年 3 月 14 到 15 日,熱門的
tj-actions/changed-files被入侵(CVE-2025-30066):攻擊者把 v1 一路到 v45.0.7 的所有版本標籤,全部改指到同一個惡意 commit,那段程式碼會去掃描 Runner 的記憶體、把找到的憑證印進建置日誌裡。超過 23,000 個儲存庫受影響,美國 CISA 也發布了專門的通報。請注意這件事最刺的地方:連釘了版本號的人都中招了,因為標籤是可以被移動的。當時唯一沒事的,是把 Action 釘在 commit SHA 上的人。
所以更保險的寫法長這樣(那串 40 位英數字就是 commit SHA):
uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1後面用註解記下它對應哪個版本,之後升級時才知道自己原本釘在哪。(這串 SHA 請自己去查當下的最新版,不要照抄本文的:在該 Action 的 GitHub 頁面點進 Releases,或直接看 tag 指向哪個 commit。)明天 Day 29 談供應鏈攻擊時,你會發現這整件事就是 SolarWinds 的縮小版:被動手腳的不是你寫的程式碼,而是你信任的那個東西。
光是掃描會跑,還不夠。如果沒有這一步,security-scan.yml 失敗時 PR 上只會顯示一個紅色叉叉當參考,Merge 按鈕依然按得下去。真正讓按鈕被物理鎖住的,是下面這個常被新手漏掉的設定:
main 新增或編輯 Branch protection rule。Run SonarQube Security Scan 加進「必要檢查」。少了這一步,你辛苦寫的 YAML 只是一個會發出警告聲的旁觀者,不是真正的門禁,這正是「有掃描」跟「有門禁」之間,最容易被忽略的一道牆。
設定好 Branch Protection 之後,當有團隊成員提交程式碼並發起 Pull Request:
security-scan.yml。
(上圖畫面為示意呈現,實際介面依 GitHub 版本而定;但 Quality Gate 鎖住 Merge 按鈕的判定邏輯,完全依照本文 YAML 設定真實運作。)
幾乎每個資深工程師都覺得自己不會犯這種低級錯誤。但 3,900 萬筆這個數字裡,絕大多數不是新手犯的錯:是趕時間時的臨時測試程式碼、是忘記加進 .gitignore 的設定檔、是複製貼上範例程式碼時忘記換成環境變數。
人不可能保持 100% 的注意力,但自動化可以。這正是 Day 08 到今天真正想傳達的邏輯遞進:IDE 級掃描負責「早期提醒」,CI 級掃描負責「最後把關」,前者靠自覺,後者不需要靠任何人自覺,因為它根本不給你選擇要不要遵守。
💬 明日預告:【Day 29】DevSecOps 落地心法:如何在開發效率與資安防守之間取得平衡?
明天我們要探討的是:連 CI 都掃過、連建置流程都通過了,2020 年的 SolarWinds 事件依然告訴我們,供應鏈安全還有一塊自動化掃描碰不到的死角。