GitHub Actions CI/CD 管線建置指南:安全設定與執行效率優化實務

Published on: | Last updated:

最近在弄 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 到網路上。這就是我們今天要聊的重點,怎麼在享受方便的同時,把門鎖好。

GitHub Actions 的 CI/CD 流程與安全檢查點示意圖
GitHub Actions 的 CI/CD 流程與安全檢查點示意圖

所以,這 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 來說,這個步驟省不得。

模擬 GitHub Actions 執行成功後的 Log 畫面
模擬 GitHub Actions 執行成功後的 Log 畫面

不同安全策略的比較...其實是取捨

說了這麼多,沒有一個是萬靈丹。老實說,全部都是在「安全性」跟「方便性」之間做取捨。

安全策略 優點 缺點(或說麻煩的點) 我自己的看法
傳統 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 看不出來,有兩招可以試試:

  1. 啟用 Debug Logging: 在 repo 的 secrets 裡新增一個 `ACTIONS_RUNNER_DEBUG`,值設為 `true`。這樣 runner 就會噴出海量的除錯訊息。有時候滿有用的,但資訊太多,要慢慢看。
  2. 終極大絕 - tmate: 這是一個超酷的 action (`mxschmitt/action-tmate@v3`)。把它加到你的 workflow step 裡,當 workflow 跑到這一步時,它會停下來,然後給你一個 SSH 連線資訊。你就可以直接 SSH 進到那台虛擬機裡面,手動執行指令、看環境變數,想幹嘛就幹嘛。這對於解一些很玄的環境問題特別有用。
安全與不安全的 CI/CD 流程概念對比
安全與不安全的 CI/CD 流程概念對比

常見的幾個坑與修正

最後整理幾個我剛開始玩的時候,自己也常犯的錯。

  • 錯誤:把雲端 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 中,最擔心的安全問題是哪一個?





Related to this topic:

Comments

撥打專線 LINE免費通話