最近在弄 GitHub Actions,嗯...這東西真的是又愛又恨。很強大,幾乎什麼自動化都能做,但同時,一個設定沒寫好,安全漏洞就跟著來。我自己是覺得,網路上很多教學都寫得很理想,複製貼上看起來都會動,但魔鬼真的都在細節裡。
先說結論
簡單講,GitHub Actions 就是你放在 GitHub repo 裡的一個不用錢的機器人,可以幫你跑測試、部署網站、甚至管基礎架構。但關鍵是,你得教會這個機器人「保護自己」,不要把家裡鑰匙(就是那些 secrets)亂丟。不然,方便的工具,很快就會變成最大的安全破口。
用 Actions 跑 Terraform?方便跟風險只有一線之隔
很多人一開始接觸 Actions,可能都是跑跑 `npm test` 之類的。但它真正的威力...是當你開始用它來碰「基礎設施」的時候,也就是所謂的 IaC (Infrastructure as Code)。
比如說,用 Terraform 來管理你的雲端資源。你看,像下面這段 YAML,就可以在你每次推程式碼到 `main` branch 的時候,自動去幫你更新 AWS 上的服務。
name: 'Terraform Apply on Main'
on:
push:
branches: [ "main" ]
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
permissions: # 這邊很重要,先給最小權限
id-token: write # 為了 OIDC
contents: read # 為了 checkout 程式碼
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubAction-AssumeRole-Production
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -no-color
- name: Terraform Apply
run: terraform apply -auto-approve -no-color
看起來很酷對吧?`git push` 一下,伺服器就自己更新好了。但...你有沒有想過,`AWS_ACCESS_KEY_ID` 和 `AWS_SECRET_ACCESS_KEY` 這些東西要放哪?如果直接寫在檔案裡,那等於是把公司大門鑰匙 po 到網路上。這就是我們今天要聊的重點,怎麼在享受方便的同時,把門鎖好。
所以,這 YAML 到底在寫什麼?
好,我們先退一步,看一下最基本的結構。所有 GitHub Actions 的設定檔,都會放在你專案根目錄下的 `.github/workflows/` 這個資料夾裡,而且都是用 YAML 格式寫的。
一個檔案就是一個 workflow,裡面大概長這樣:
- name: 就...這個 workflow 的名字,讓你自己在 Actions 頁面看得懂。
- on: 觸發條件。可以是 `push` 程式碼、有人開 `pull_request`,甚至是 `schedule` 排程(例如每天半夜三點)。
- jobs: 真正要執行的工作。一個 workflow 可以有好幾個 job。
- runs-on: 指定這個 job 要跑在哪種作業系統的虛擬機上,常用的是 `ubuntu-latest`。這些機器是 GitHub 提供的,叫 GitHub-hosted runners。
- steps: 一個 job 裡面包含很多步驟。每個步驟可以是用 `run` 來執行 shell 指令,或是用 `uses` 來呼叫別人寫好的現成動作(action)。
像 `actions/checkout@v4` 就是官方寫好的 action,專門用來把你的程式碼從 repo 拉到虛擬機裡。幾乎是每個 workflow 的第一步。
把門鎖好:幾個必須知道的安全設定
OK,終於到重點了。我自己是覺得,下面這幾點,比學會寫複雜的 workflow 還重要。因為功能寫錯了,頂多是動不了;但安全搞錯了,後果可能很嚴重。
第一關:Secrets and OIDC
最一開始,大家都是用 Secrets。就是到 repo 的 Settings > Secrets and variables > Actions 裡面去新增一組一組的鍵值對,像是 `API_TOKEN` 或 `AWS_SECRET_ACCESS_KEY`。然後在 workflow 裡用 `${{ secrets.MY_SECRET_NAME }}` 來讀取。
這方法...嗯,能用。但它有個根本問題:你還是得自己去產生一個「永不過期」的 key,然後手動貼上去。這個 key 一旦外洩,在被你發現然後手動刪除之前,都是有效的。
所以,現在更推薦的方法是 OIDC (OpenID Connect)。概念有點複雜,但簡單講,就是讓 GitHub Actions 直接去跟雲端服務商(像 AWS, GCP, Azure)溝通,拿一個「暫時的、有時效性的」token。整個過程不需要任何永久的 key。上面那個 Terraform 範例用的就是 OIDC,你看它完全沒有出現 `secrets.AWS_KEY` 之類的東西,安全很多。
這點 GitHub 官方文件寫得非常詳細,但老實說,初學者可能會看不太懂。反而是去看一些國外大神,像是 Google Cloud 或 AWS 官方部落格的文章,他們會有針對自己平台的 OIDC 設定教學,跟著做一遍會比較有感覺。
第二關:最小權限原則
每個 workflow job 跑的時候,都會被賦予一個叫 `GITHUB_TOKEN` 的東西,讓它可以跟你的 repo 互動,例如留言在 PR 上。問題是,預設情況下,這個 token 的權限是「幾乎全開」的。
如果你的 workflow 只是跑個測試,根本不需要寫入權限吧?萬一你用的某個第三方 action 有問題,它就可能用這個權限去亂搞你的 repo。
所以,養成好習慣,在每個 job 裡面加上 `permissions` 區塊,明確告訴它這個 job 只需要哪些權限。
permissions:
contents: read # 只需要讀取 repo 內容
pull-requests: write # 需要在 PR 上留言
id-token: write # 如果要用 OIDC,就需要這個
什麼都不需要的話,就寫個 `permissions: {}`,把它關乾淨。
第三關:鎖定你的 Action 版本
我們很常用別人寫好的 action,像是 `actions/checkout@v4`。但這個 `@v4` 其實是指向 `v4` 這個 tag 最新的版本。如果有一天,這個 action 的作者(或駭客)把 `@v4` 這個 tag 指到一個有問題的版本,你的 workflow 就會跟著中獎。
最安全,但也是最囉唆的做法,是直接鎖定到某個 commit 的 SHA hash。
# 不太好,tag 是可以被移動的
- uses: actions/checkout@v4
# 最好,直接鎖死某個 commit
- uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675
我知道這很煩,每次更新都要去查 SHA。但對於處理敏感資料的 workflow 來說,這個步驟省不得。
不同安全策略的比較...其實是取捨
說了這麼多,沒有一個是萬靈丹。老實說,全部都是在「安全性」跟「方便性」之間做取捨。
| 安全策略 | 優點 | 缺點(或說麻煩的點) | 我自己的看法 |
|---|---|---|---|
| 傳統 Secrets | 設定超簡單,直覺。 | Key 是長期的,要手動管理和輪替,外洩風險高。 | 嗯...小專案或測試還行,但正式生產環境,真的不建議了。 |
| OpenID Connect (OIDC) | 不用管 long-lived key,是目前最安全的方式。 | 第一次設定比較複雜,要搞懂雲端服務的 IAM Role。 | 這是趨勢,也是正確的方向。痛一次把設定搞懂,後面就很輕鬆。 |
| 鎖定 Action 版本 (Pinning SHA) | 完全避免第三方 action 被偷換內容的風險。 | 更新 action 版本會變得很麻煩,要一直手動去查 commit SHA。 | 我自己是覺得,重要的 production workflow 一定要鎖。但一些非核心的,或許鎖到次版本號 (`@v4.1.1`) 也就夠了。 |
| 自動化安全掃描 (SAST/DAST) | 讓機器人幫你抓低級錯誤,像是把密碼寫死在程式碼裡。 | 可能會有很多誤報 (false positive),需要花時間去判斷。 | 這是必備的防護網。CodeQL、tfsec 這種工具,至少裝一個,跑心安也好。 |
當 Actions 跑失敗...怎麼救?
寫 workflow 嘛,十次有八次第一次都會跑失敗。除錯是家常便飯。
最基本的就是去看 log。Actions 頁面會把每一步的輸出都印出來,大部分問題都能從這裡找到線索。
如果 log 看不出來,有兩招可以試試:
- 啟用 Debug Logging: 在 repo 的 secrets 裡新增一個 `ACTIONS_RUNNER_DEBUG`,值設為 `true`。這樣 runner 就會噴出海量的除錯訊息。有時候滿有用的,但資訊太多,要慢慢看。
- 終極大絕 - tmate: 這是一個超酷的 action (`mxschmitt/action-tmate@v3`)。把它加到你的 workflow step 裡,當 workflow 跑到這一步時,它會停下來,然後給你一個 SSH 連線資訊。你就可以直接 SSH 進到那台虛擬機裡面,手動執行指令、看環境變數,想幹嘛就幹嘛。這對於解一些很玄的環境問題特別有用。
常見的幾個坑與修正
最後整理幾個我剛開始玩的時候,自己也常犯的錯。
- 錯誤:把雲端 access key 直接寫在 YAML 裡。
修正:拜託不要。最少最少要用 GitHub Secrets,但最好是花點時間研究一下怎麼用 OIDC。 - 錯誤:`permissions` 區塊懶得寫,用預設值。
修正:養成習慣,複製貼上別人的 workflow 時,第一件事就是檢查 `permissions`。用不到的就關掉,只給 job 真正需要的最小權限。 - 錯誤:一個 workflow 檔案塞了十幾個 job,做一堆不相干的事。
修正:盡量讓一個 workflow 專注做一件事。例如,「測試」是一個 workflow,「部署到 Staging」是另一個,「部署到 Production」又是另一個。這樣比較好管理,也比較好除錯。可以用 `workflow_dispatch` 來手動觸發,或用 `workflow_run` 來串連不同的 workflow。 - 錯誤:重複的步驟在每個 workflow 都複製貼上。
修正:如果有很多步驟都一樣(例如 `setup-node`、`npm install`),可以考慮把它們包成一個 Composite Action,放在 `.github/actions/` 目錄下。這樣就可以在各個 workflow 裡重複使用,改起來也方便。
說到底,GitHub Actions 就是一個工具。工具本身沒有好壞,端看你怎麼用它。花點時間在安全設定上,絕對是個划算的投資。至少,半夜不用擔心被叫起來,說公司的 AWS 帳號被拿去挖礦了...對吧。
聊聊你的看法:你在 GitHub Actions 中,最擔心的安全問題是哪一個?
