Skip to content

GitHub Actions 進 tailnet:用 OAuth client 建 ephemeral runner

用 tailscale/github-action 把 GitHub Actions workflow 連進 tailnet:OAuth client 與 tag、workload identity federation、self-hosted runner 的差異,以及只開必要 port 的最小 grants 範例與「不要照抄全開 grants」的提醒

Updated View as Markdown

GitHub Actions 的 workflow 跑在 GitHub 代管的 VM 上,本來碰不到你家裡或公司內網的服務。用 Tailscale GitHub Action 可以在 workflow 裡加一個 step,讓它以一台 ephemeral(用完即丟)的節點加入你的 tailnet,之後的 step 就能連到 tailnet 內的其他機器——部署到內部伺服器、讀測試資料庫、連 self-hosted runner 都行。

這篇講怎麼設、三種認證方式(OAuth client、workload identity federation、auth key)的差異,以及最重要的:用 grants 把這台 runner 的權限收到最小,不要照抄示範的全開規則。cloud agent 連回 tailnet 的通則(tag 身分、會過期的憑證、grants 只開 HTTPS),先看 雲端 agent sandbox 連回 tailnet 的通用做法。

本文依 2026 年 10 月的 Tailscale 官方文件、GitHub 文件與 tailscale/github-action 原始碼(action.yml)整理,以 Tailscale v1.104 為準。github-action 以 v4 為目前主版本(tag v4 存在,最新 minor 為 v4.2.0;action.yml 的 version 輸入預設 1.94.2)。workload identity federation 需要 Tailscale 1.90.1 以上。本文未在 GitHub 代管 runner 實機跑過整套流程,屬文件整理。

這個 action 是什麼

tailscale/github-action@v4 做的事很單純:在 workflow 裡加一個 step,它會裝 Tailscale、用你給的憑證把這台 runner 以 tag 身分加入 tailnet,等 workflow 跑完再登出。這台節點是 ephemeral——登出後控制平面會自動把它從 tailnet 移除,不會在 Machines 頁面堆積。有開 device approval 的 tailnet,它建立的節點也會是 pre-approved,不用每次等管理員核准。

用 tag 身分是重點:GitHub runner 不是任何一個使用者,它必須靠 tag 才能套用你為它寫的存取規則。tag 的概念見 Tags 與 auth key。

主要輸入

action 的輸入都定義在 action.yml,比較常用的是:

輸入 說明
oauth-client-id OAuth client 或 OIDC federated identity 的 client ID
oauth-secret OAuth client secret(走 workload identity federation 時不需要)
audience OIDC federated identity 的 audience(走 workload identity federation 時用)
tags 逗號分隔的 tag 清單,至少一個;OAuth client/federated identity 要有 auth_keys scope 且 tag 要相符
authkey 舊的 auth key 認證方式,action.yml 已標棄用,官方建議改用 OAuth client
version Tailscale 版本,預設 1.94.2,可設 latest 或 unstable
ping 連上後要用 tailscale ping 驗證的主機清單(IP 或 MagicDNS 名稱),最多等 3 分鐘
use-cache 是否快取 Tailscale 二進位檔,v4 起預設 true
log-mode grouped(預設)/normal/quiet
statedir state 目錄;留空用記憶體。開 Tailnet Lock 的 tailnet 要指定

注意 oauth-client-id 一個欄位同時扛兩種用途:走 OAuth client 時放 client ID,走 workload identity federation 時放 federated identity 的 client ID。差別在第二個欄位——OAuth client 用 oauth-secret,federated identity 用 audience。

tags 的比對不只「完全相同」:指定的 tag 可以等於 trust credential 上的 tag,也可以是 credential 的 tag 所 own 的其他 tag(README 的 Troubleshooting)。若遇到 requested tags [tag:...] are invalid or not permitted,多半是 credential 沒有 auth_keys scope、或 tag 不符合這條規則。

三種認證方式

方式一:OAuth client(最常用)

在 Tailscale 建一個 OAuth client,scope 給 auth_keys 的 Write,並指定它能用哪些 tag。client ID 與 secret 存成 GitHub secret,workflow 裡:

- name: Tailscale
  uses: tailscale/github-action@v4
  with:
    oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
    oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
    tags: tag:ci

OAuth client 的建立、auth_keys scope、tag 必填、secret 只顯示一次等細節,見 OAuth client。

方式二:workload identity federation(無長效 secret)

OAuth client 的 secret 是長效憑證,要一直保管。如果你願意多設一點,可以用 workload identity federation 丟掉這把 secret:改用 GitHub 發的短效 OIDC token(JWT)證明身分,action 拿它向 Tailscale 換取建立節點的權限。

permissions:
  id-token: write # 讓 action 向 GitHub 索取 JWT,federation 一定要開

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Tailscale
        uses: tailscale/github-action@v4
        with:
          oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
          audience: ${{ secrets.TS_AUDIENCE }}
          tags: tag:ci
  • 建立的是 federated identity(Trust credentials 裡的 OpenID Connect 型),拿到的 client ID 與 audience 都不是秘密,還是可以存進 GitHub secret 免得外露。
  • 需要 Tailscale 1.90.1 以上。
  • 和 OAuth client 一樣,federated identity 要有 auth_keys scope、要指定 tag。
  • federation 的機制(--client-id、--audience 等)見 OAuth client 的「Workload identity federation」一節。

方式三:auth key(已棄用)

authkey 輸入仍可用,但 action.yml 已標上 deprecationMessage,官方建議改用 OAuth client。真要用的話,key 要是 reusable、ephemeral、帶 tag(有 device approval 再開 pre-approved)。另外開 Tailnet Lock 的 tailnet 不能用 OAuth client 認證,得用 pre-signed auth key 並指定 statedir。

設定步驟(OAuth client 路線)

  1. 定義 tag:在 policy 的 tagOwners 定義 tag:ci。
  2. 建 OAuth client:Trust credentials 頁面建 OAuth client,scope 勾 auth_keys 的 Write,tag 選 tag:ci。記下 client ID 與 secret(secret 只顯示一次)。
  3. 存 GitHub secret:把 client ID 存成 TS_OAUTH_CLIENT_ID、secret 存成 TS_OAUTH_SECRET。
  4. 加 step:workflow 裡加上面的 Tailscale step,tags: tag:ci。
  5. 驗證連線:因為新節點在 tailnet 的傳播有延遲(eventual consistency),官方建議用 ping 輸入列出要連的目標,或在後續 step 手動 tailscale ping <目標>。

最小權限的 grants 範例

這是最容易出錯的一步。新 tailnet 的預設政策是全開——任何裝置都能連到任何裝置的任何埠。GitHub runner 一進來,如果不特別處理,它就能連到 tailnet 內幾乎所有東西。有些示範為了方便,會貼出 "src": ["*"], "dst": ["*"] 這種全開 grants——那是示範用,不要照抄進 production。

正確做法是只放行 runner 需要的服務與埠。假設 tag:ci 只需要部署到一台 build server 的 HTTPS:

"tagOwners": {
  "tag:ci": ["autogroup:admin"],
  "tag:build-server": ["autogroup:admin"],
},
"grants": [
  // CI runner 只能連 build server 的 HTTPS,其他一律連不到
  {
    "src": ["tag:ci"],
    "dst": ["tag:build-server"],
    "ip": ["tcp:443"],
  },
],
  • 需要 SSH 部署就把 ip 加上 tcp:22,但只加真正需要的埠。
  • 確認 policy 裡沒有殘留的全開規則("*" 到 "*")——Tailscale 的 grants 只放行、不阻擋,留著全開規則,上面的限制形同虛設。
  • 雙重最小權限:OAuth client 的 scope 只管「能建哪個 tag 的節點」,grants 管「那個 tag 的節點能連到哪裡」,兩層都要收緊。規則寫法見 ACL / Grants 入門。

self-hosted runner 的差異

上面講的是 GitHub 代管 runner。如果你用自己的 self-hosted runner:

  • GitHub 代管 runner 每次都是乾淨的 VM,跑完就丟;self-hosted runner 是持續存在的機器,跑完 workflow 後 action 只會停掉 Tailscale 背景程序、把二進位檔留在機器上,不會整台重置。
  • 代管 runner 節點 ephemeral、離線即移除;self-hosted runner 若不特別處理,可能以 tag 身分一直掛在 tailnet 上,等於一台常駐的 CI 機器——權限要另做考量。
  • runner 映像檔版本:action 以 node24 發布(action.yml 的 runs.using),README 要求 runner image >= 2.237.1 才能跑 Node.js 24。GitHub 代管 runner 會自動更新,最常踩到這點的是 self-hosted runner——版本太舊要先升級 runner 映像檔。
  • 官方文件對「self-hosted runner 持續存在的差異」講得不多,部署前以當時的 action README 為準。

小結

認證方式 憑證 長效 secret 何時用
OAuth client client ID + secret 有 最常見、設定簡單
workload identity federation client ID + audience 無 想丟掉長效 secret 時
auth key reusable auth key 有 已棄用;或 Tailnet Lock

骨架都一樣:tag 身分 → ephemeral 節點 → grants 只開必要埠。真正的差別在憑證由誰背書,以及你有沒有那把要長期保管的 secret。不管選哪種,都記得把 grants 收緊、別照抄全開示範。

參考資料

Navigation

Type to search…

↑↓ navigate↵ selectEsc close