iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Modern Web

前端來點 AWS 技能樹!系列 第 24 篇

Day24 - OIDC:讓 GitHub Actions 向 AWS 換一把短期鑰匙

  • 分享至 

  • xImage
  •  

昨天最後留下一個問題:要讓 GitHub Actions 幫我們 push Image、通知 EC2 換版,GitHub 的機器要怎麼拿到 AWS 的權限?
今天就來設定 OIDC,讓 workflow 每次執行時才向 AWS 換一組短期的權限,GitHub 上完全不用存 access key ㄅ!

為什麼不把 access key 存進 GitHub?

昨天有說到最直接的做法,是幫 GitHub Actions 建一個 IAM User,產生一組 access key 存進 GitHub 的 Secrets,workflow 執行時再拿出來用。這樣確實可行,但跟今天要用的 OIDC 比,有各自的優缺點:

access key 存在 GitHub OIDC
GitHub 上存了什麼 可以直接操作 AWS 的 access key 只有 Role 的 ARN,它不是密碼
有效多久 直到手動停用為止 每次執行才換一組,預設 1 小時後失效
外洩了會怎樣 拿到的人可以一直用,直到我們發現 沒有長期的 key 可以偷,換到的權限也很快就過期
誰能使用 任何拿到 key 的人 只有指定的 repo 和 branch
要不要定期更換 要,而且很容易忘記 不用,每次都是新的

OIDC 是什麼?

OIDC 的全名是 OpenID Connect,是一套用來證明「我是誰」的標準。套到我們的情境,就是由 GitHub 會開一張身分證明給 workflow,而 AWS 認得這張證明,就發短期的權限給它。
可以想成你要去某棟公司大樓參加研討會:大會的工作人員會給你參加證明,而你要拿著這張證明給警衛確認,就換給你一張當天有效的識別證,而能進到哪幾層樓和用哪些服務也都寫在上面。對照起來是這樣:

大會參加者 OIDC
參加證明 GitHub 發的身分證明(ID token),寫著「我是哪個 repo、哪個 branch 的 workflow」
警衛 AWS STS,專門發短期權限的服務
警衛認得的證明 IAM 的身分提供者(Identity provider)
大會參與者名單 Role 的信任政策(trust policy)
當天有效的識別證 短期的 access key,預設 1 小時後失效
參與者能進哪幾層樓 Role 的許可政策(permissions policy)

實際跑起來的流程是這樣:

  1. workflow 開始執行,先跟 GitHub 要一張身分證明,上面有 GitHub 的簽章,沒辦法偽造。
  2. workflow 拿著身分證明去找 AWS STS,說要使用 gha-deploy-role 這個 Role。
  3. AWS 確認簽章真的是 GitHub 簽的,再看證明上的 repo、branch 有沒有在這個 Role 的名單上。
  4. 都符合,就發一組短期的 access key 給 workflow,後面的步驟就用它來操作 AWS。

所以今天在 AWS 上要做兩件事:讓 AWS 認得 GitHub 發的身分證明,以及建一個 Role、寫好名單。至於這個 Role 能做什麼,下一篇寫 deploy.yml 時再來加。

把專案放上 GitHub

如果專案還沒放上 GitHub,先在 GitHub 建一個新的 repo(建議選 Private,原因在最後的資安小提醒),再把 Day9 的 Next.js 專案 push 上去。
等一下會用到 GitHub 帳號名稱和 repo 名稱,這篇用 my-account 和 my-app 當範例。

讓 AWS 認得 GitHub

先到 IAM 的 Console 新增一個 Identity providers:

  1. 左邊選單點 Identity providers(身分提供者),按 Add provider(新增提供者)。
  2. Provider type 選 OpenID Connect。
  3. Provider URL 填 https://token.actions.githubusercontent.com。
  4. Audience 填 sts.amazonaws.com。
  5. 按 Add provider(新增提供者)。

https://ithelp.ithome.com.tw/upload/images/20261008/20179793IjFWItQmX7.png

  • Audience 是身分證明上寫的「這張要給誰看」,用 AWS 官方的 configure-aws-credentials 時,固定是 sts.amazonaws.com。
  • 同一個 AWS 帳號只要建一次,之後其他 repo 的 Role 都可以共用。

PS. 比較舊的教學會要你按 Get thumbprint 取得憑證指紋,現在已經不用了,AWS 會用自己信任的憑證機構清單去驗證 GitHub 的憑證。

建立給 GitHub Actions 用的 Role

一樣在 IAM 的 Console:

  1. 左邊選單點 Roles(角色),按 Create role(建立角色)。
  2. Trusted entity type 選 Web identity(Web 身分)。
  3. Identity provider 選 token.actions.githubusercontent.com,Audience 選 sts.amazonaws.com。
    https://ithelp.ithome.com.tw/upload/images/20261008/20179793w4cpP3ikI9.png
  4. 下面會多出三個欄位:GitHub organization 填自己的 GitHub 帳號名稱(個人帳號也填這格),GitHub repository 填 my-app,GitHub branch 填 main,按 Next。
    https://ithelp.ithome.com.tw/upload/images/20261008/20179793l1CsqFyDki.png
  5. Add permissions 這一頁什麼都不選,直接按 Next。
  6. Role name 填 gha-deploy-role,按 Create role(建立角色)。
  7. 點進剛建好的 Role,複製頁面上的 ARN,長得像 arn:aws:iam::123456789012:role/gha-deploy-role。
  • 第 4 步的 repository 和 branch 雖然是選填,但留空就會變成 *,代表這個帳號底下任何 repo、任何 branch 都能用這個 Role,所以這兩格一定要填。
  • 第 5 步不給權限是故意的:今天只確認 GitHub 換得到這個 Role,下一篇用到什麼權限,再加什麼。

建好之後,點擊你剛剛建的 role 進到 Role 的 Trust relationships(信任關係) Tab,就能看到 Console 幫我們寫好的名單:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:my-account/my-app:ref:refs/heads/main"
                }
            }
        }
    ]
}
欄位 意思
Federated 只認在"讓 AWS 認得 GitHub"新增的 GitHub 身分提供者
Action 允許拿身分證明來換這個 Role 的權限
aud 這張證明是要給 AWS STS 的
sub 哪個 repo、哪個 branch 的 workflow 才能使用,也就是名單本身

為什麼一定要有 sub?因為全世界的 GitHub repo 拿到的身分證明,都是同一個 GitHub 發的,aud 也都一樣。只看 aud 的話,任何人的 workflow 都能用我們的 Role。所以 IAM 規定,信任 GitHub 的 Role 一定要寫 sub 條件,而且不能只寫 *,不然連建都建不起來。

把 Role 的 ARN 存進 GitHub

到 GitHub 上的 repo:

  1. 點 Settings,左邊選單找到 Secrets and variables,點 Actions。
  2. 切到 Variables Tab,按 New repository variable。
  3. Name 填 AWS_ROLE_ARN,Value 貼上剛剛複製的 ARN,按 Add variable。

https://ithelp.ithome.com.tw/upload/images/20261008/201797936BPZOi4W6L.png
為什麼放 Variables 而不是 Secrets?ARN 只是 Role 的名字,不是密碼,別人就算知道,也過不了名單那一關。放在 Variables 之後還看得到值,要改也方便;Secrets 存進去之後就看不到了。

寫一個最小的 workflow 試試看

在專案裡新增 .github/workflows/oidc-test.yml:

name: OIDC test

on:
  workflow_dispatch:

permissions:
  id-token: write

jobs:
  whoami:
    runs-on: ubuntu-latest
    steps:
      - name: Show OIDC subject
        run: echo "repo:${GITHUB_REPOSITORY_OWNER}@${GITHUB_REPOSITORY_OWNER_ID}/${GITHUB_REPOSITORY#*/}@${GITHUB_REPOSITORY_ID}:ref:${GITHUB_REF}"

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: ap-east-2

      - name: Who am I
        run: aws sts get-caller-identity

逐段來看:

  • on: workflow_dispatch:不會自動執行,要到 GitHub 上手動按按鈕才會跑,測試時比較好控制。
  • permissions:id-token: write 是允許 workflow 跟 GitHub 要身分證明,用 OIDC 一定要有這行。只要加了 permissions 之後,沒列出來項目的權限都會變成沒有,因為這個 workflow 不用讀程式碼,所以只寫這一行就好。
  • jobs:workflow 實際要做的工作,可以有好幾個 job,這裡只有一個;whoami 是我們自己取的名字,會顯示在 Actions 的執行畫面上。
    • job 跑在 GitHub 提供的 Ubuntu Linux 機器(Runner)上;latest 是 GitHub 目前指定的版本
    • 每次執行都開一台全新的機器,跑完就收掉,換到的短期 access key 也不會留下來
    • 機器上已經裝好 AWS CLI,所以後面可以直接打 aws 指令
  • steps:在這台機器上依序執行的步驟,run 是直接執行指令,uses 是套用別人寫好的 action;接著再用每個步驟的 name 逐一說明
    • Show OIDC subject:用 GitHub 提供的環境變數,組出這個 repo 的 sub,等一下對答案用。${GITHUB_REPOSITORY#*/} 這行是把 my-account/my-app 前面的 my-account/ 去掉,只留下 repo 名稱。
    • Configure AWS credentials:AWS 官方的 action,會幫我們跑完前面說的流程(要身分證明、找 STS 換權限)。aws-region 填自己用的 region,這個系列是台北 ap-east-2。
    • Who am I:用換到的權限問 AWS「我現在是誰」。

把這個檔案 commit、push 到 main 之後,就可以手動執行了:

  1. 到 GitHub repo 的 Actions Tab,左邊點 OIDC test。
  2. 按右邊的 Run workflow,Branch 選 main,再按綠色的 Run workflow。

第一次執行為何被擋下來了?

如果你的 repo 跟這篇練習一樣是新建的,可能會看到紅燈,Configure AWS credentials 這一步出現:

Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity

前面還會有好幾行 Retry AssumeRole: attempt 1 of 12 failed,這個 action 失敗時會自動重試,所以要等一下才會停。
Not authorized 的意思是:AWS 收到了身分證明,簽章也沒問題,但上面的 sub 不在這個 Role 的名單上。對照一下兩邊:

來源 sub
Role 的名單(Console 產生的) repo:my-account/my-app:ref:refs/heads/main
Show OIDC subject 印出來的 repo:my-account@1234567/my-app@123456789:ref:refs/heads/main

差別在帳號名稱和 repo 名稱後面,各多了 @ 加一串數字。

這是 GitHub 在 2026 年 7 月 15 日的改動:這天之後建立的 repo,sub 會在帳號和 repo 名稱後面,加上它們的數字 ID。原因是名稱可以改,舊名稱空出來之後,可能被別人註冊走;如果名單只認名稱,新主人就能用同一個名稱通過檢查。數字 ID 不會換人用,加上去就不怕被冒用。(如果你的 repo 是 7 月 15 日之前建的,第一次就會是綠燈,可以直接跳到下面看 Who am I 的結果。)

修正的方式是把名單改成新格式:

  1. 回到 IAM,點 Roles(角色) 裡剛剛建的 gha-deploy-role,切到 Trust relationships(信任關係) 分頁,按 Edit trust policy(編輯信任政策)。
  2. 把 token.actions.githubusercontent.com:sub 那一行的值,換成 Show OIDC subject 印出來的那一串。
  3. 按 Update policy(更新政策)。

回到 GitHub 剛剛失敗的那次執行,按 Re-run jobs,選 Re-run all jobs(如果還是失敗,等幾秒讓 IAM 的設定生效再試一次)。這次就會是綠燈了,Who am I 會印出:

{
    "UserId": "AROAEXAMPLEROLEID1234:GitHubActions",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/gha-deploy-role/GitHubActions"
}

assumed-role/gha-deploy-role/GitHubActions 的意思是:現在是用 gha-deploy-role 這個 Role 的身分在操作,GitHubActions 是這次使用的名稱,configure-aws-credentials 預設就是這個。
這樣問題就來了:這個 Role 明明還沒有任何權限,為什麼問得出來?
因為 AWS 規定「問自己是誰」不需要任何權限。也就是說,GitHub 現在已經換得到身分了,只是還什麼都不能做。

換個 branch 執行

名單上只寫了 main,那我們換成其他 branch 呢?可以試試看:

  1. 在 repo 首頁點 branch 選單,輸入 test-oidc,建立一個新的 branch。
  2. 到 Actions Tab 按 Run workflow,這次 Branch 選 test-oidc。
  3. 一樣會在一分鐘左右後失敗。看 Show OIDC subject 印出來的那串,結尾變成了 ref:refs/heads/test-oidc,跟名單對不上。
  4. 試完到 branch 列表把 test-oidc 刪掉。
  • 這就是名單的用途:就算有人可以 push 到這個 repo,只要程式碼還沒進到 main,就拿不到 AWS 的權限。下一篇的 deploy.yml 也會用這個 Role,等於只有 merge 進 main 的程式碼才能部署。
  • 我自己的 side project 一開始寫的名單是 repo:<owner>/<repo>:*,也就是這個 repo 的任何 branch 都可以,後來回頭檢查時才收緊成只限 main。當時雖然沒出事,但「任何 branch」代表任何能 push 的人,開一個新 branch、改一下 workflow,就能拿到部署的權限。名單能寫多窄,就要寫多窄喔!

錯誤訊息對照

照著做卡住的話,可以先對一下這張表:

看到的錯誤 原因
Not authorized to perform sts:AssumeRoleWithWebIdentity sub 對不上:新舊格式、branch、帳號或 repo 名稱打錯(大小寫也要一樣)
No OpenIDConnect provider found in your account 在"讓 AWS 認得 GitHub"的身分提供者沒建,或 Provider URL 打錯
Credentials could not be loaded 漏了 id-token: write,或 AWS_ROLE_ARN 沒設定、名稱打錯

今天新增的身分提供者和 Role 都不用錢;GitHub Actions 在 private repo 每個月有免費的執行時間,這篇的 workflow 跑一次只用到一兩分鐘。

現在 GitHub Actions 已經換得到 AWS 的身分了,但這個 Role 還什麼都不能做。下一篇就來寫 deploy.yml:push 到 main 之後自動 build、推上 ECR、通知 EC2 換版,用到哪些權限,就幫這個 Role 加哪些ㄅ!

資安小提醒:repo 建議設成 Private,因為 Public repo 的 Actions logs 所有人都看得到。另外,能用 OIDC 的地方,就不要再把 access key 存進 GitHub 喔!

參考資料


上一篇
Day23 - IaC 與 CI/CD:把手動的步驟交給程式
系列文
前端來點 AWS 技能樹! 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言