CI に人間用のパスワードを置きたくない ── service token の発行と GitHub Actions からの pull

上原正吉(EarthLink Network Co., Ltd.)。AI会社・AI社員による業務自動化と、人の逐次介入なしに24時間自動開発を回す仕組みを自社で実践しています。開発ノウハウと具体的な設定方法、運用して分かった改善点を公開しています。

CI に人間用のパスワードを置きたくない ── service token の発行と GitHub Actions からの pull

CI に人間用のパスワードを置きたくない ── service token の発行と GitHub Actions からの pull

この記事は、連載「dotvault の使い方 ── .env を手で配るのをやめ、CI・チーム・AI エージェントに 1 コマンドで配るまで」の第 7 回です。この連載が終わる時に、dotvault を誰でも無料で使える形で公開する予定です。

GitHub Actions で build する時に秘密の値が要ると、.env の中身を 1 行ずつ GitHub の Secrets に貼ることになります。DATABASE_URL、STRIPE_SECRET_KEY、Sentry の DSN、と貼っていき、値を取り替えた日には dotvault と GitHub の両方を直します。もう 1 つのやり方は、自分のアカウントで login した状態を CI に持たせることですが、それでは CI が自分の権限で動き、自分のパスワードか login の token が CI の側に置かれます。

「CI に秘密を渡すには、値そのものか、自分の login を CI に置くしかない」と考えがちですが、CI に置くのは dotvault の service token 1 つで足ります。値は dotvault に置いたままで、CI は実行のたびに dotvault pull で受け取ります。token が漏れたら console で Revoke すれば、次の実行から受け取れなくなります(この回の Actions では、Revoke の後に同じ workflow を再実行すると、pull の段で AUTH_REQUIRED と終了コード 3 で止まり、build まで進みませんでした)。

この回では、console で service token を発行し、GitHub の Secrets に 1 つだけ置き、GitHub Actions から production の値を pull して、Revoke で止めるところまでを、Actions のログで順に見ていきます。

結論

  • service token は console で発行します。 サイドバーの Service Tokens → Create token で、Name・Role・Project・Expiry を選びます。CLI の dotvault token create は console に案内するだけで、発行はできません。CI には Role を Read only、Project をその project 1 つにし、Expiry を選びます(既定は Never=期限なしです)。
  • CI に置くのは DOTVAULT_TOKEN 1 つです。 発行した直後に 1 回だけ出る est_… を GitHub の Secrets に入れ、workflow では dotvault pull demo-app --env production を実行します。project-config.json が無いリポジトリでも、project の名前を付ければ受け取れます。
  • この token で届くもの・届かないもの。 Read only なので書き込みは拒否され、demo-app 以外の project の名前を指定しても拒否されます(存在しない名前で確かめました)。Global Keys の pool を見ることもできません。token を絞る単位は project で、同じ project の development と staging も pull できます。
  • Revoke すると、次の実行から止まります。 pull と run は ✖ AUTH_REQUIRED: invalid service token(終了コード 3)、API を直接呼ぶと HTTP 401 です。dotvault whoami は手元の token の形を見るだけなので、token が生きているかは pull で確かめます。

本文(読了 約11分)

前提と注意

  • 読者。 2 回目から使っている demo-app を、GitHub Actions から pull します。6 回目の「同じ SENTRY_DSN を project ごとに貼っている ── 共通の値は Global Keys に置いて bind する」で、demo-app に Global Keys の値を 2 つ bind した状態(CF_API_TOKEN と SENTRY_DSN_WEB)から続けます。この回の撮影の前に、production に DATABASE_URL と STRIPE_SECRET_KEY(どちらもダミー)を set しました。
  • 環境。 画面は本番の app.dotvault.io と CLI 0.15.7 です。token を使う端末は Node 20 の Linux コンテナで、login もせず、OS のパスワード保管庫(keychain)も使わず、環境変数の DOTVAULT_TOKEN だけを渡しました(CI と同じ条件です)。GitHub Actions は、この回のために作った private のリポジトリで実行しました。
  • 端末と Actions のログは再構成です。 生の出力(Actions は gh run view --log で取ったログ)を文字どおりに写し、見やすい形に組み直しています。Actions のログの時刻と、npm install の警告の行は省き、省いた所は「…」の 1 行にしました。生の出力に [exit=N] として残した終了コードは、echo $? の行で示しています。
  • token は伏せています。 est_ で始まる token は、画面でも端末でも est_•••• に置き換えました。この回で発行した token は、撮影の後に 2 本とも Revoke しています。
  • 画面は英語です。 管理コンソールの表示言語は、2 回目の付録の手順で日本語にできます。
  • デモのアカウントです。 project 名、key の値、org の id は、この連載のために作ったデモのものです。dummy-… や cf_demo_… は、どこにも使えないダミーの値です。
  • 用語。 「service token」は、人の login の代わりに CI やサーバーが使う、dotvault のトークンです(est_ で始まります)。「Revoke」は、その token を無効にすることです。「Secrets」は、GitHub のリポジトリに秘密の値を置く場所で、workflow からは ${{ secrets.名前 }} で読みます。「step」は workflow の中の 1 段の処理です。「終了コード」は、コマンドが終わる時に返す数で、0 が成功、それ以外が失敗です。

1. Service Tokens 画面と Create token

サイドバーの Service Tokens を開くと、まだ 1 本も無い状態ではこう出ます。

Service Tokens 画面の空状態。上に「Non-interactive IDs for CI / Lambda / SDKs. The secret is shown only once, at issuance. If one leaks, revoke it immediately. Everything is recorded in Audit.」の注意書き、Create token ボタン、「Use a token by exporting DOTVAULT_TOKEN=est_… for the CLI/SDK.」の案内と、「No service tokens yet.」。

Create token を押すと、項目は Name・Role・Project・Expiry の 4 つです。既定は Role が Read only、Project が All projects(org 全体)、Expiry が Never です。

Create service token のモーダルの既定。Name は空(例として ci-deploy が薄く出る)、Role は Read only (viewer)、Project は All projects (whole org)、Expiry は Never。

Role は 3 つから選びます。画面の表記と、括弧の中の名前(dotvault の中での役の名前)は次のとおりです。

画面の表記画面の説明CI に使うか
Read only (viewer)Can pull secrets only. Minimal privilege for reading in CI.使う(この回)
Read/write (member)In addition to pull, can add, update, and delete keys.CI から値を書く時だけ
Admin / automation (provisioner)Can also perform admin operations such as creating projects and changing settings. Intended for Claude Code headless onboarding.使わない(8 回目で扱う)

Admin / automation を選ぶと、漏れた時の影響が大きいという警告が出て、Project を選んでも org 全体が対象になる、という注記に変わります。

Role に Admin / automation (provisioner) を選んだモーダル。説明の下に「provisioner is a strong credential that can perform admin operations such as creating projects and changing settings. Because the impact of a leak is large, be careful who it is issued to and how it is stored.」の警告、Project の下に「provisioner only performs admin operations such as creating projects, so its scope is the whole org regardless of this choice」の注記。

Expiry の選択肢は Never・30 days・90 days・1 year です。既定の Never のまま作ると、期限の無い token になります。同じ console の Help は「期限は短く」と書いているので、CI に渡す token は期限を選んで作ります。

2. token を発行する

CI 用に、Name を ci-deploy、Role を Read only、Project を demo-app、Expiry を 90 days にしました。

ci-deploy を作る直前のモーダル。Name に ci-deploy、Role は Read only (viewer)(Can pull secrets only. Minimal privilege for reading in CI.)、Project は demo-app、Expiry は 90 days。

Create を押すと、est_ で始まる token が 1 回だけ表示されます。Copy で写して、すぐに GitHub の Secrets に入れます(5 節)。この画面を閉じると、同じ token は二度と表示されません。下の画像は、同じ手順で作ったもう 1 つの token(ci-nightly)の画面で、token の文字を伏せてから撮りました。

Token created の画面(token は伏せ字)。「Copy this token now — it is shown only once and cannot be retrieved again.」の注意、est_•••• の token、Copy と Done のボタン。

Done を押すと、一覧に 1 行増えます。ci-deploy の Expires は、Created の 90 日後になっています。

Service Tokens の一覧に 2 本。ci-nightly(Read only (viewer)・demo-app・期限は作成の 30 日後・active)と ci-deploy(Read only (viewer)・demo-app・期限は作成の 90 日後・active)。各行に Revoke ボタン。

service token の考え方(発行時に 1 回だけ表示されること、漏れたら Revoke すること)は、以前の記事「secretを安全に配る ── .env を1コマンドで配る dotvault を作りました」でも紹介しました。この回は、その手順を実機で通します。

3. CLI では発行できない

CLI から token を作ろうとすると、console に案内されます。

dotvault token create --name ci-deploy の端末画面(再構成)。「dotvault token create is no longer available in the CLI.」「Service tokens are created, listed, and revoked in the dotvault admin console:」「https://app.dotvault.io/admin/tokens」「The token API only accepts admin-console sessions, so a CLI login cannot manage tokens.」と、終了コード 1。

token list も 1 行目のコマンド名が token list になるだけの同じ案内で、終了コード 1 です。発行・一覧・Revoke は、どれも console の Service Tokens 画面で行います。

4. token だけで pull してみる

GitHub に置く前に、token だけで受け取れることを手元で確かめます。login していない端末で、環境変数の DOTVAULT_TOKEN に token を入れて実行しました。ディレクトリには demo-app の project-config.json だけを置いています。

token だけの端末画面(再構成)。dotvault whoami が「Service token (DOTVAULT_TOKEN=est_••••••••…) — CI / Lambda mode」、dotvault pull -E production が「✓ Wrote 5 keys to /work/ci-run/.env (project=demo-app, env=production, file=.env).」と「✔ 1 file synced, 1 excluded, 5 keys total」、cat .env で CF_API_TOKEN・SENTRY_DSN_WEB・API_BASE_URL・DATABASE_URL・STRIPE_SECRET_KEY の 5 行。

whoami は、login した時の「メールアドレス(token expires in …s)」の代わりに、service token で動いていることを示します。pull -E production は 5 key を書きました。production に project の key として置いたのは DATABASE_URL と STRIPE_SECRET_KEY の 2 つで、API_BASE_URL は project の Common (_shared) の値、先頭の 2 つ(CF_API_TOKEN と SENTRY_DSN_WEB)は 6 回目に bind した Global Keys の値です。「1 excluded」は、project-config.json に書いてある .env.local が production には無いため、書かなかったという意味です。

dotvault run -E production -- <コマンド> も、同じ token で通りました(値をファイルに書かずにコマンドに渡す形です)。

5. GitHub Actions に渡す

Secrets に token を置く

リポジトリの Settings → Secrets and variables → Actions の New repository secret で、名前を DOTVAULT_TOKEN、値を 2 節で写した token にします。この回は、GitHub の CLI で同じことをしました。

gh secret set DOTVAULT_TOKEN -R <owner>/<repo>
# 入力を求められたら token を貼り付ける

GitHub の Secrets に置くのは、この 1 つだけです。DATABASE_URL などの値は GitHub には置きません。

workflow

.github/workflows/deploy.yml を次のようにしました。2 つ目の step で dotvault を入れて、production を pull します。

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Pull dotvault secrets
        env:
          DOTVAULT_TOKEN: ${{ secrets.DOTVAULT_TOKEN }}
        run: |
          npm install -g @earthlink/dotvault
          dotvault whoami
          dotvault pull demo-app --env production
          cut -d= -f1 .env

      - name: Your build step
        run: npm run build

dotvault pull demo-app --env production は、project の名前を付けているので、リポジトリに project-config.json が無くても受け取れます。cut -d= -f1 .env は、key の名前と、dotvault が書く目印の行(# BEGIN と # END)だけを出して、値をログに出さないための行です。npm run build は、この回のために作った小さな build で、.env を読んで DATABASE_URL・STRIPE_SECRET_KEY・API_BASE_URL が入っているかを set / MISSING で出します(値は出しません)。pull は値を .env に書くだけで、次の step の環境変数にはしません。この build は .env を自分で読むので値が入りましたが、.env を読まない build では値は入りません。そういう build では、build の中で必要な key が入っているかを確かめる行を置いてください。

実行のログ

main に push すると、Actions が実行されました。

GitHub Actions の実行ログ(再構成)。Pull dotvault secrets の step で、whoami が「Service token (DOTVAULT_TOKEN=est_••••••••…) — CI / Lambda mode」、pull が「✓ Wrote 5 keys to /home/runner/work/dotvault-ci-demo/dotvault-ci-demo/.env (project=demo-app, env=production, file=.env).」と「✔ 1 file synced, 0 excluded, 5 keys total」、目印の行と key の名前 5 つ。Your build step で DATABASE_URL: set、STRIPE_SECRET_KEY: set、API_BASE_URL: set、build finished。

DOTVAULT_TOKEN: *** は、GitHub が Secrets の値を伏せて表示している所です。whoami の行の est_ に続く 8 文字は、GitHub は伏せず、ログに残ります(画像では伏せています)。ここでは 0 excluded でした。リポジトリに project-config.json を置いていないので、.env 1 つだけを書いたためです。

6. この token で届くもの、届かないもの

ci-deploy(Read only・demo-app・90 days)で、届くものと届かないものを確かめました。

token の届く範囲の端末画面(再構成)。set FOO=1 -E production は「✖ PERMISSION_DENIED: … (server: service token is read-only (viewer); write requires a member token).」で 4、pull nonexistent-proj -E production は「(server: service token is not scoped to this project)」で 4、global status は「(server: no access to the org global key pool)」、pull -E development は 8 keys total、pull -E staging は 5 keys total で成功。

操作結果終了コード
set FOO=1 -E production(書き込み)PERMISSION_DENIED(service token is read-only (viewer); write requires a member token)4
delete STRIPE_SECRET_KEY -E production同じ PERMISSION_DENIED4
pull nonexistent-proj -E production(demo-app 以外の名前)PERMISSION_DENIED(service token is not scoped to this project)4
global status(Global Keys の pool を見る)PERMISSION_DENIED(no access to the org global key pool)4
pull -E production5 keys0
pull -E development8 keys(.env 7 + .env.local 1)0
pull -E staging5 keys0

書き込みと、demo-app 以外の project は止まりました。この org には demo-app 以外の project が無いので、他の project の確認は、存在しない名前で行っています。サーバーの答えは「この project に絞られていない」という理由でした。Global Keys の pool も、この token からは見えません。

token を絞る単位は project です。同じ token で、production のほかに development(8 key)と staging(5 key)も受け取れました。CI に渡す token は、その CI が受け取ってよい値を持つ project に絞って発行します。

7. CLI を使わずに受け取る(curl)

CLI を入れられない環境では、API を直接呼べます。token は HTTP の Authorization ヘッダーに Bearer <token> の形で付けて渡します。

curl の端末画面(再構成)。GET https://api.dotvault.io/v1/env/pull?project=demo-app&env=production&file=.env に Authorization: Bearer $DOTVAULT_TOKEN を付けると、project・env・file・keys(5 つの key と値)・source(shared: API_BASE_URL、envSpecific: DATABASE_URL と STRIPE_SECRET_KEY、global: CF_API_TOKEN と SENTRY_DSN_WEB)の JSON。token を付けないと {"error":"UNAUTHORIZED","message":"Missing token"} と HTTP 401。

応答の source に、どの key がどこから来たかが出ます。global に CF_API_TOKEN と SENTRY_DSN_WEB があり、4 節の pull に入った先頭の 2 つが、6 回目に bind した Global Keys の値だと API の側でも分かります。file を付けないと、files の配列の中に同じ形で入って返りました。成功は HTTP 200 です。

8. Revoke で止める

一覧の行の Revoke を押すと、ブラウザの確認が出ます(ブラウザが出す確認の窓なので、画像はありません)。

Revoke token “ci-deploy”? CI jobs and services using it will lose access immediately.

OK を押すと、Status が revoked になります。一覧の絞り込みは Active only(既定)・All (incl. revoked)・Revoked only の 3 つで、Revoked only にすると Revoke した token だけが出ます。

Revoked only で絞った Service Tokens の一覧。ci-deploy(Read only (viewer)・demo-app・revoked)の 1 行だけ。Revoke ボタンは無い。

Revoke の直後に、5 節の実行を再実行しました(gh run rerun を使いました。GitHub の画面の Re-run jobs と同じ操作です)。

GitHub Actions の再実行のログ(再構成・Revoke の後)。Pull dotvault secrets の step が失敗の印で、whoami は「Service token (DOTVAULT_TOKEN=est_••••••••…) — CI / Lambda mode」、pull は「✖ AUTH_REQUIRED: invalid service token」、「Error: Process completed with exit code 3.」。Your build step は実行されない。

pull の段で終了コード 3 で止まり、build の step は実行されませんでした。手元の端末でも同じです。

Revoke の後の端末画面(再構成)。dotvault whoami は「Service token (…) — CI / Lambda mode」で 0、pull -E production と run -E production はどちらも「✖ AUTH_REQUIRED: invalid service token」で 3、curl は {"error":"UNAUTHORIZED","message":"invalid service token"} と HTTP 401。

whoami は、Revoke の後も「CI / Lambda mode」と出て、0 を返しました。whoami は手元にある token の形を見ているだけで、サーバーに問い合わせていないためです。token が生きているかどうかは、pull が通るかで確かめます。

Revoke の後のエラーは PERMISSION_DENIED ではなく AUTH_REQUIRED です。でたらめな est_ の値で pull しても、同じ AUTH_REQUIRED: invalid service token(終了コード 3)でした。CI の失敗の通知でこの文を見たら、token が Revoke されたか、Secrets の値が違うかを疑います。Secrets が空の時(名前の間違いなど)は、「AUTH_REQUIRED: Not logged in. Run dotvault login first.」(終了コード 3)になりました。

9. うまくいかない時の出力

操作出力終了コード
dotvault token create / token listdotvault token create / dotvault token list is no longer available in the CLI. … console へ案内1
Read only の token で set / deletePERMISSION_DENIED(read-only (viewer); write requires a member token)4
demo-app 以外の名前を pull(存在しない名前で確認)PERMISSION_DENIED(not scoped to this project)4
Revoke 後・でたらめな est_ の値で pull / runAUTH_REQUIRED: invalid service token3
DOTVAULT_TOKEN が空のまま pull(Secrets の名前の間違いなど)AUTH_REQUIRED: Not logged in. Run dotvault login first.3
Revoke 後の whoamiService token (…) — CI / Lambda mode(手元の token の形だけを見る)0
token 無しで API を呼ぶ{"error":"UNAUTHORIZED","message":"Missing token"}HTTP 401

pull は値を .env に書くだけなので、.env を読まない build や、key の名前を打ち間違えた build は、値が無いまま進むことがあります。build の中に、必要な key が入っているかを確かめる行を 1 つ置いておくと、すぐに気づけます(5 節の cut -d= -f1 .env や、この回の build の set / MISSING の行です)。

次回

8 回目は、AI エージェントに token を渡す回です。値を書ける Read/write の token と、1 節で警告を見た Admin / automation の token の 2 本を新しく発行して AI エージェントに渡し、project の作成から pull までを人の login なしで進めます。


dotvault は、.env や設定ファイルの secret をサーバー側に置き、権限のある人・CI・AI エージェントが 1 コマンドで受け取るサービスです(https://dotvault.io)。この連載が終わる時に、dotvault を誰でも無料で使える形で公開する予定です。公開したら、この連載の手順どおりに使い始められます。

興味のある方は、ぜひ「いいね」と記事の購読をお願いいたします。

自社プロダクトは https://www.eln.ne.jp/products にまとめています。

筆者について

上原正吉。EarthLink Network Co., Ltd. 代表。AI社員に日々の業務を任せ、開発では人が逐一指示しなくても作業が進むHOOTL(Human Out Of The Loop)の仕組みを自社で実践しています。このブログでは、業務自動化と24時間自動開発のノウハウ、具体的な設定方法、日々の運用で見つかった問題と改善を共有します。

また、AIで業務や開発を組み替えたい会社・チーム向けに、AI活用のコンサルティングも受け付けています。ご相談は www.eln.ne.jp からどうぞ。


EarthLink Network は、会社の全業務を AI で回すために、必要になったものを自社で作っています。いま作っているプロダクトの一覧と概要は、こちらにまとめています。

→ EarthLink Network が自社でつくっている18のプロダクト

会社と各プロダクトの詳細は、公式サイト www.eln.ne.jp をご覧ください。