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 為目前主版本(tagv4存在,最新 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:ciOAuth 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_keysscope、要指定 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 路線)
- 定義 tag:在 policy 的
tagOwners定義tag:ci。 - 建 OAuth client:Trust credentials 頁面建 OAuth client,scope 勾
auth_keys的 Write,tag 選tag:ci。記下 client ID 與 secret(secret 只顯示一次)。 - 存 GitHub secret:把 client ID 存成
TS_OAUTH_CLIENT_ID、secret 存成TS_OAUTH_SECRET。 - 加 step:workflow 裡加上面的 Tailscale step,
tags: tag:ci。 - 驗證連線:因為新節點在 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 收緊、別照抄全開示範。
參考資料
- tailscale/github-action(官方 repo,action.yml 與 README)
- Tailscale GitHub Action(官方 KB)
- Connect GitHub CI/CD workflows to private infrastructure(官方)
- Workload identity federation(官方)
- OAuth clients(官方)
- Auth keys(官方)
- OAuth client:給 CI、operator 與雲端 agent 用的憑證
- 雲端 agent sandbox 連回 tailnet 的通用做法
- Tags 與 auth key:給伺服器用的非互動登入
- ACL / Grants 入門