Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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` でパイプごとスキップされ無視される。
Expand Down
37 changes: 37 additions & 0 deletions LESSONS.md
Original file line number Diff line number Diff line change
@@ -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 が未署名バイナリ等で
Expand Down