From 0f8f785eb6d1f0d8a1808f3447f14a441fbb36d0 Mon Sep 17 00:00:00 2001 From: yugosasaki Date: Sat, 15 Aug 2026 14:57:23 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore(ci):=20Scoop=20=E7=94=A8=20PAT=20?= =?UTF-8?q?=E3=82=92=201Password=20=E3=81=8B=E3=82=89=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E5=87=BA=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Secrets に直接置いていた SCOOP_GITHUB_TOKEN を、1Password の `gh` vault(op://gh/SCOOP_GITHUB_TOKEN/credential)からリリース時に読み出す方式へ 移行する。GitHub 側に残る長期シークレットは 1Password サービスアカウントの トークンのみになり、PAT のローテーションは 1Password 側だけで完結する。 load-secrets-action は v5 で export-env が既定 false のため、outputs 経由で goreleaser ステップにのみ渡す。旧 `secrets.SCOOP_GITHUB_TOKEN` の行は残さず 置換する。step レベルの env が勝つため、併存させたまま GitHub 側の secret を 削除すると空文字が渡り、goreleaser の scoop push が不可解な認証エラーで落ちる。 .goreleaser.yaml は `{{ .Env.SCOOP_GITHUB_TOKEN }}` のままで変更不要。 op-check.yml は移行の検証用の一時ジョブ。`skip_upload: auto` によりプレリリース では scoop アップロード自体がスキップされ、タグを打ってもトークンを試せない。 本番リリースを消費せずに push 権限を確かめるため、このブランチ限定で走らせる。 検証後に削除する。 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/op-check.yml | 30 ++++++++++++++++++++++++++++++ .github/workflows/release.yml | 18 +++++++++++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/op-check.yml diff --git a/.github/workflows/op-check.yml b/.github/workflows/op-check.yml new file mode 100644 index 0000000..d67250c --- /dev/null +++ b/.github/workflows/op-check.yml @@ -0,0 +1,30 @@ +# 一時ジョブ: 1Password から Scoop PAT を読み出し、scoop-bucket への push 権限を確認する。 +# 本番リリースを消費せずに移行の成否を確かめるためのもの。確認できたらこのファイルは削除する。 +name: op-check + +on: + push: + branches: [chore/scoop-pat-via-1password] + +permissions: {} + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Load Scoop PAT from 1Password + id: op + uses: 1password/load-secrets-action@v5 + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + SCOOP_GITHUB_TOKEN: op://gh/SCOOP_GITHUB_TOKEN/credential + + - name: Verify push access to kwrkb/scoop-bucket + env: + GH_TOKEN: ${{ steps.op.outputs.SCOOP_GITHUB_TOKEN }} + run: | + # fine-grained PAT が対象リポジトリをリソースに含まない場合、API は 403 ではなく + # 404 を返す。呼び出しの成否ではなく permissions.push を明示的に確認する。 + PUSH="$(gh api repos/kwrkb/scoop-bucket -q .permissions.push)" + echo "push permission: $PUSH" + [ "$PUSH" = "true" ] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b56061a..cb0e972 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,15 +31,27 @@ jobs: echo "Release ${GITHUB_REF_NAME}" > /tmp/release-notes.md fi + # Scoop の manifest は別リポジトリ kwrkb/scoop-bucket へ push するため、 + # 当該リポジトリにしか権限が無い既定の GITHUB_TOKEN では届かない。専用の PAT が要る。 + # その PAT は GitHub Secrets ではなく 1Password の `gh` vault で管理し、ここで読み出す。 + # GitHub 側に置く長期シークレットは 1Password サービスアカウントのトークン 1 つだけになる。 + # + # export-env は既定 false(v5)。環境変数にばら撒かず outputs 経由で + # goreleaser ステップにだけ渡す。 + - name: Load Scoop PAT from 1Password + id: op + uses: 1password/load-secrets-action@v5 + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + SCOOP_GITHUB_TOKEN: op://gh/SCOOP_GITHUB_TOKEN/credential + - uses: goreleaser/goreleaser-action@v7 with: version: "~> v2" args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Scoop の manifest は別リポジトリ kwrkb/scoop-bucket へ push するため、 - # 当該リポジトリにしか権限が無い既定の GITHUB_TOKEN では届かない。 - SCOOP_GITHUB_TOKEN: ${{ secrets.SCOOP_GITHUB_TOKEN }} + SCOOP_GITHUB_TOKEN: ${{ steps.op.outputs.SCOOP_GITHUB_TOKEN }} # GoReleaser の `--release-notes` は changelog パイプ内で読み込まれるため、 # .goreleaser.yaml の `changelog.disable: true` でパイプごとスキップされ無視される。 From 399f2d5089fc32c435a889f7a1cb47d276116916 Mon Sep 17 00:00:00 2001 From: yugosasaki Date: Sat, 15 Aug 2026 14:58:18 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore(ci):=20=E6=A4=9C=E8=A8=BC=E7=94=A8?= =?UTF-8?q?=E3=81=AE=20op-check=20=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run 31868231450 で 1Password 経由の PAT 読み出しと kwrkb/scoop-bucket への push 権限(permissions.push = true)を確認できたため、役割を終えた。 --- .github/workflows/op-check.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/op-check.yml diff --git a/.github/workflows/op-check.yml b/.github/workflows/op-check.yml deleted file mode 100644 index d67250c..0000000 --- a/.github/workflows/op-check.yml +++ /dev/null @@ -1,30 +0,0 @@ -# 一時ジョブ: 1Password から Scoop PAT を読み出し、scoop-bucket への push 権限を確認する。 -# 本番リリースを消費せずに移行の成否を確かめるためのもの。確認できたらこのファイルは削除する。 -name: op-check - -on: - push: - branches: [chore/scoop-pat-via-1password] - -permissions: {} - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Load Scoop PAT from 1Password - id: op - uses: 1password/load-secrets-action@v5 - env: - OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} - SCOOP_GITHUB_TOKEN: op://gh/SCOOP_GITHUB_TOKEN/credential - - - name: Verify push access to kwrkb/scoop-bucket - env: - GH_TOKEN: ${{ steps.op.outputs.SCOOP_GITHUB_TOKEN }} - run: | - # fine-grained PAT が対象リポジトリをリソースに含まない場合、API は 403 ではなく - # 404 を返す。呼び出しの成否ではなく permissions.push を明示的に確認する。 - PUSH="$(gh api repos/kwrkb/scoop-bucket -q .permissions.push)" - echo "push permission: $PUSH" - [ "$PUSH" = "true" ] From 41ae4eb5d328e5d9ec64c2684d009e458e487742 Mon Sep 17 00:00:00 2001 From: yugosasaki Date: Sat, 15 Aug 2026 15:03:30 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs(lessons):=20Scoop=20=E7=94=A8=20PAT=20?= =?UTF-8?q?=E3=81=AE=201Password=20=E7=A7=BB=E8=A1=8C=E3=82=92=E8=A8=98?= =?UTF-8?q?=E9=8C=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vault 単位でしかアクセス権を切れない制約から専用 vault を切った判断と、 skip_upload: auto によりプレリリースでは検証にならない点を残す。 --- LESSONS.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/LESSONS.md b/LESSONS.md index 4fddd8a..ec84fbe 100644 --- a/LESSONS.md +++ b/LESSONS.md @@ -1,5 +1,42 @@ # LESSONS +## Scoop 用 PAT を 1Password 管理へ移行 (2026-08-15) + +### CI シークレットの「移行先」は vault 単位の到達範囲で選ぶ +- 1Password サービスアカウントのアクセス権は **vault 単位**で付き、個々の item に絞れない。手元の + `Dev` vault を GitHub Actions 用 SA にそのまま与えると、Actions 環境が漏れた際の到達範囲が Scoop + PAT だけでなく `Dev` の全 item に広がる。実際、無関係な複数プロジェクトの本番シークレットが同居して + いた +- **却下した案**: (A) `actions/create-github-app-token` — installation token は 1 時間で失効し + `scoop-bucket` にスコープできるため長期 PAT を完全に廃止できるが、SA と `gh` vault は作成済みで + PAT 格納済みだった。(B) 既存 `Dev` vault を GitHub 用 SA に付与 — 追加作業ゼロだが上記の到達範囲 + 問題がある。(C) `secrets.SCOOP_GITHUB_TOKEN` の行を残したまま 1Password 読み出しを併存 — step + レベルの `env:` が job env より優先されるため、GitHub 側の secret を削除した時点で空文字が渡り、 + goreleaser の scoop push が認証エラーで落ちる +- **決め手**: `gh` 専用 vault を切って Scoop PAT だけを入れ、GitHub 用 SA には read-only でその vault + のみ付与した。これで移行後に GitHub 側に残る長期シークレットは `OP_SERVICE_ACCOUNT_TOKEN` 1 つに + なる。**シークレットの数は減っていない**(1 個が 1 個に置き換わっただけ)。得られるのはローテーション + の一元化と 1Password 側の監査ログで、そこを理由に採る限り妥当 +- **覆す条件**: SA トークンの失効でリリースが黙って壊れた場合、または GitHub 側に長期シークレットを + 一切置かない要件が出た場合は GitHub App 方式(案 A)へ移す + +### `skip_upload: auto` のせいでプレリリースタグは検証にならない +- 配布チャネルのトークンを差し替えたら普通はプレリリースタグで試したくなるが、`.goreleaser.yaml` の + `scoops.skip_upload: auto` はプレリリース時に bucket アップロードごとスキップする。`v*-rc1` を打っても + **ジョブは緑になるがトークンを一切試していない**。release.yml のコメントが警告している「リリースは + 公開済みだが bucket 未更新」という半端な状態を、本番タグで初めて踏むことになる +- **決め手**: ブランチ限定(`on: push: branches: [...]`)の一時ジョブ `op-check.yml` を置き、1Password + から PAT を読んで `gh api repos/kwrkb/scoop-bucket -q .permissions.push` を確認する方式で検証した。 + run 31868231450 で `push permission: true` を得てからファイルを削除している。本番リリースを 1 回も + 消費せずに「op 参照が解決するか」「SA が vault に届くか」「PAT に push 権限があるか」を分離して潰せる +- **ルール**: fine-grained PAT が対象リポジトリをリソースに含まない場合、GitHub API は 403 ではなく + **404** を返す。権限確認では API 呼び出しの成否を見ず、`permissions.push` の値を明示的に判定する +- 併せて `gh api repos/kwrkb/scoop-bucket/branches/main -q .protected` が `false` であることも確認した。 + collaborator 権限があっても branch protection があれば goreleaser の直接 push は弾かれるため、 + push 権限の確認だけでは経路が通る保証にならない +- **覆す条件**: `skip_upload` を外してプレリリースでも bucket を更新する運用に変えたら、プレリリース + タグ自体が経路の検証になるため一時ジョブは不要になる + ## WinGet から Scoop へ切替: Defender 誤検知 (2026-08-12) **「WinGet 採用」の結論を差し替える。** 覆す条件として書いた「WinGet の PR が未署名バイナリ等で