-
Notifications
You must be signed in to change notification settings - Fork 0
171 lines (160 loc) · 8.63 KB
/
Copy pathsecurity.yml
File metadata and controls
171 lines (160 loc) · 8.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# 安全掃描。
#
# 四種掃描,各回答一個別人回答不了的問題:
#
# | | 問什麼 |
# |---|---|
# | **CodeQL** | 這份原始碼裡有沒有已知形狀的漏洞(注入、路徑穿越、憑證誤用…) |
# | **govulncheck** | 我們**實際呼叫得到**的路徑上有沒有已知的 Go 漏洞 |
# | **Gitleaks** | 有沒有憑證被提交進來 |
# | **Trivy** | 相依樹與設定檔裡有沒有已知問題 |
#
# **每週排程不是多餘的。** 隨 `ci.yml` 觸發的那一次回答的是「我這次改動有沒有
# 帶進問題」,排程回答的是「**沒有動過的程式**今天有沒有變成有問題的」——
# 新的 advisory 是對著既有的相依發布的,而那時候沒有人在推 commit。
# 少了排程,一個服務可以整季不被掃到。
#
# **本倉庫是公開的,所以隨 `ci.yml` 觸發的那一次不經過任何閘門**——掃描與基礎
# 驗證同時起跑(見 `ci.yml` 的 `security` job)。排程仍然是獨立的第二個進入點:
# 它有自己的時鐘,沒有人推 commit 的那幾週也照跑。
#
# ## SARIF 上傳為什麼容許失敗
#
# 私有倉庫的 code scanning 需要 GitHub Advanced Security。沒有啟用時
# `upload-sarif` 會以 403 收場,而那**不是這次掃描的結論**——它是帳務狀態。
# 所以上傳那一步 `continue-on-error: true`,而 SARIF **一律**另存成 artifact:
# 有 GHAS 的倉庫在 Security 分頁看結果,沒有的從 artifact 下載,
# 兩邊都不會出現「掃了但結果不知道去哪了」。
#
# 掃描本身的失敗**不**容許——那才是這支 workflow 的結論。
name: 安全掃描
on:
# **本支不自己掛 push 與 pull_request**,改由 `ci.yml` 的 `security` job
# 呼叫。**本倉庫是公開的,所以那個 job 沒有 `needs:`**——掃描與基礎驗證
# 同時起跑,不等它綠燈。私有倉庫留著那道閘(分鐘數要付錢),
# 判準見 workspace `AGENTS.md` §1.9.1。
#
# 既然不必表達順序了,為什麼還留著 `workflow_call`,不乾脆自己掛
# push/pull_request?因為那會把檢查名稱從 `安全掃描 / Gitleaks` 變回
# `Gitleaks`,動到按名字釘住的分支保護;而且同一顆 commit 會變成兩次 run,
# PR 的檢查清單裡就少了「這一組屬於同一次驗證」這件事。留在同一次 run 裡、
# 只是沒有 `needs:`,拿到的並行是一樣的,這兩個代價一個都不用付。
#
# 用 `workflow_call` 而不是 `workflow_run` 也是刻意的:`workflow_run` 只讀
# **預設分支**上的 workflow 檔,所以改這支檔案的 PR 驗不到自己的改動,
# 而且它的執行不會掛回那個 PR 的檢查清單。本地 `uses: ./…` 讀的是
# **被呼叫那一顆 commit** 的檔案,兩件事都不會發生。
workflow_call:
schedule:
# 每週一次。各倉庫的分鐘與小時刻意錯開,十五個倉庫同時醒來只會互相排隊。
#
# 排程是**獨立於 `ci.yml` 的第二個進入點**。它回答的是「沒有動過的程式
# 今天有沒有變成有問題的」,而新的 advisory 發布的那天沒有人在推 commit——
# 所以它要有自己的時鐘,不依賴任何一次 push。
- cron: "47 3 * * 1"
workflow_dispatch:
# **這裡刻意沒有 `concurrency:`。**
#
# 被 ci.yml 呼叫時,called workflow 讀到的 `github` context 是**呼叫端的**——
# `github.workflow` 會是 "CI",所以照抄 ci.yml 那組
# `${{ github.workflow }}-${{ github.ref }}` 會算出**與呼叫端完全相同**的群組,
# 變成同一次執行跟自己搶號碼牌(`cancel-in-progress` 為真時是自己取消自己)。
#
# 它也不需要:被呼叫時這些 job 屬於 ci.yml 那一次 run,ci.yml 的 concurrency
# 取消那次 run 會一併取消它們。剩下的兩個進入點是每週排程(一週一次、單一 ref)
# 與手動觸發(人明確按下去的,不該被安靜取消)。
# 預設只讀。要寫 code scanning 的 job 自己在 job 層加 security-events: write,
# 這樣 gitleaks 與 govulncheck 這種不上傳的 job 就拿不到那個權限。
permissions:
contents: read
jobs:
gitleaks:
name: Gitleaks
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# 掃整個歷史,不只這一顆 commit:一個被刪掉的憑證仍然在歷史裡,
# 而它仍然是一個憑證。
fetch-depth: 0
# 用釘住的 release 執行檔而不是 action:gitleaks 的官方 action 對組織
# 帳號要授權金鑰,而這件事不值得多一個要管的密鑰。校驗碼比對過才解壓,
# 否則「釘住版本」只擋得住換版本,擋不住換內容。
- name: 安裝 gitleaks 8.30.1
run: |
set -euo pipefail
curl -fsSL -o gitleaks.tar.gz \
"https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz"
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks.tar.gz" | sha256sum -c -
tar -xzf gitleaks.tar.gz gitleaks
chmod +x gitleaks
# `gitleaks git`,不是 `detect`——後者在 v8.19 就被拆成 `git` 與 `dir` 了,
# 而 v8.30 已經不認得它。誤報的允許清單在倉庫根目錄的 .gitleaks.toml,
# gitleaks 會自己讀。
#
# `--log-opts=HEAD` 把範圍收到**被 checkout 的那一條分支的完整歷史**。
# 少了它,掃的是這個 clone 裡的**所有 ref**——而上面的 `fetch-depth: 0`
# 會把每一條遠端分支都抓下來,所以任何一條還沒合併的分支上的誤報,
# 都會讓這個倉庫之後**每一個 PR** 變紅,而紅燈的位置與內容跟那個 PR
# 的 diff 毫無關係。一個與自己無關的紅燈,看的人只會學會忽略它。
#
# 這**不是**把檢查放寬:`--log-opts=HEAD` 掃的仍然是完整歷史
# (「一個被刪掉的憑證仍然在歷史裡」那句照舊成立),而帶著問題的那條
# 分支自己被 checkout 時一樣會紅——它開 PR 時照樣擋得下來。
- name: 掃描
run: ./gitleaks git --redact --report-format sarif --report-path gitleaks.sarif --exit-code 1 --log-opts=HEAD .
- name: 保留報告
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: gitleaks-sarif
path: gitleaks.sarif
retention-days: 30
if-no-files-found: warn
trivy:
# **在 PR 上也跑**(裁示 2026-08-21)。
#
# 這裡從 2026-08-16 起有一條 `if: github.event_name != 'pull_request'`,
# 理由是「找到的東西是修一次就結束的,在 PR 攔下與在 `main` 攔下代價一樣,
# 所以省下每個 PR 數分鐘」。**那句話的前半仍然成立,後半不再是收益**:
# 升上 Enterprise 之後分鐘數不是稀缺的東西了。
#
# 而代價一直都在:一個在 `main` 才被攔下的問題,是一個**已經合併**的問題。
# 判準因此收斂成一句——**CI 做得到的驗證,就讓它在合併前跑。**
#
# 每週排程仍然留著,它回答的是另一個問題:**沒有動過的程式**今天有沒有
# 變成有問題的(見上方 `on:` 的註解)。
name: Trivy
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 掃描相依與設定
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: fs
scanners: vuln,secret,misconfig
format: sarif
output: trivy.sarif
# 掃描本身不因為發現東西而失敗——嚴重度的判斷交給讀報告的人。
# 真正該擋下建置的是 govulncheck(它只報呼叫得到的)。
exit-code: "0"
ignore-unfixed: true
- name: 上傳到 code scanning(沒有 GHAS 時會失敗,不影響結論)
continue-on-error: true
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
sarif_file: trivy.sarif
category: trivy
- name: 保留報告
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: trivy-sarif
path: trivy.sarif
retention-days: 30
if-no-files-found: warn