共有秘密をローテーションしても端末を締め出さない鍵配布サーバ設計
全PCへAPIキーを自動配布する小さなサーバで、共有秘密の張り替え時に全端末が永久401になる事故を防ぐ設計と復旧手順。primary/legacy/enrollの3層認証、クライアント側の上書き更新分岐、ローテーションの正しい順序と実測方法
約3.6万トークンの節約 (API料金換算で約54円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「共有秘密をローテーションしても端末を締め出さない鍵配布サーバ設計」は、業務自動化カテゴリのAI指示書(MDファイル)です。全PCへAPIキーを自動配布する小さなサーバで、共有秘密の張り替え時に全端末が永久401になる事故を防ぐ設計と復旧手順。primary/legacy/enrollの3層認証、クライアント側の上書き更新分岐、ローテーションの正しい順序と実測方法この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.6万トークン(API料金換算で約54円)・86%のトークンを節約できます。
- カテゴリ
- 業務自動化
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約4.2万トークン
- この巻物使用時
- 約6,000トークン
- 節約量
- 約3.6万トークン (約54円)
- 更新日
- 2026-08-26
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/md-762bfe1e/raw を読み込んで、この指示書どおりに実装して"
中身
共有秘密をローテーションしても配布先PCを締め出さない鍵配布サーバの設計と復旧手順
チームの全PCへ AI/API のキーを自動配布する小さなサーバ(以下 keyserve)を運用するときの、 「共有秘密を張り替えた瞬間に全端末が永久に締め出される」事故を防ぐ設計と、 すでに締め出してしまった場合の復旧手順をまとめた指示書。
対象読者: AI エージェント(Claude Code / Codex 等)にこの作業をやらせる人。 前提スタック: Node.js のサーバレス関数(Vercel/Cloudflare/Lambda いずれでも可)+ 各PCで動く配布クライアント(Node スクリプト)。
1. 最小構成(これだけで動く)
- サーバ:
POST /api/keysのみ。認証は HMAC-SHA256(共有秘密, unixtime) をヘッダで受ける。x-<prefix>-ts(unix 秒)とx-<prefix>-auth(hex 64桁)。|now - ts| <= 300秒、比較はcrypto.timingSafeEqual。- 秘密は環境変数に置く。配布リポジトリが public なら暗号文であっても鍵をリポジトリに置かない。
- サーバの返り値:
{ files: { "<ファイル名>": "<中身>", ... } }。クライアントは~/.<app>/配下へ書く。 - クライアント: 1日1回だけ実行(前回実行時刻を state ファイルに保存して間引く)。失敗しても起動処理を止めない。
認証の受け口は必ず「複数本」にする
const candidates = [
...(process.env.SHARED_SECRET ? [{ secret: process.env.SHARED_SECRET, label: 'primary' }] : []),
...list(process.env.SHARED_SECRET_LEGACY).map((s, i) => ({ secret: s, label: `legacy#${i + 1}` })),
...list(process.env.ENROLL_SECRETS).map((s, i) => ({ secret: s, label: `enroll#${i + 1}` })),
];
// list() は JSON 配列 / 改行 / カンマ区切りのどれでも受ける
primary: 現行の正規の秘密。サーバは応答に必ずkeyserve.env(= primary の入ったファイル)を含めて返す。 こうすると「一時トークンで1回だけ叩けば、以後は正規の秘密で通る」状態を自動で作れる。legacy: 移行期間だけ有効な旧秘密。ローテーションの生命線。enroll: 新規端末・締め出された端末の受け入れ用の使い捨てトークン。使い終わったら消す。- 一致したラベルだけをログに出す(値は絶対に出さない)。
2. 事故の型: 「既存ならスキップ」に更新対象を乗せてはいけない
配布クライアントは事故防止のため、こう書かれがち:
if (fs.existsSync(destination)) continue; // ← 既存は絶対に上書きしない
fs.writeFileSync(destination, contents, { flag: 'wx', mode: 0o600 });
手で編集した API キーを潰さないという意味では正しい。だが、この既定に
「サーバ側で変わりうる値」= 共有秘密そのもの(keyserve.env)を乗せると詰む。
サーバの primary を張り替えた瞬間に、各PCは古い秘密で叩き続ける →401 → 新しい秘密を受け取る手段が無い →
永久に自己回復できない。しかも失敗はログにしか出ないので数日誰も気付かない。
正しい実装(更新対象だけ明示的に上書き分岐)
const cleaned = contents.replace(/^/, '');
if (fs.existsSync(destination)) {
// 秘密ファイルだけは中身が変わっていたら更新する。他の配布ファイルは従来どおりスキップ。
if (name !== 'keyserve.env' || fs.readFileSync(destination, 'utf8') === cleaned) continue;
fs.writeFileSync(destination, cleaned, { encoding: 'utf8', mode: 0o600 });
log(`refreshed: ${name}`); // 値は出さない
continue;
}
fs.writeFileSync(destination, cleaned, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
- 中身が同じなら書かない(mtime を動かさない)= 無駄な差分と再起動を誘発しない。
401かつ秘密ファイルが存在する場合は、可視メッセージに 「サーバ側で秘密がローテーションされた可能性があります」を添える。原因に直結する一文があるだけで復旧が早い。- 失敗は必ず人の見る場所(チャットの通知先)へ1行出す。ログファイルだけに出す実装が「数日誰も気付かない」の原因。 ただし24時間に1回に抑止し、通知本文に秘密値を含めない。
3. ローテーションの正しい順序(②から始めない)
- クライアント修正を先に配布(上の上書き分岐)。配布経路は git pull / zip 取得など、 鍵の認証に依存しない経路であること。ここが認証に依存していると鶏卵になる。
primaryを新しいランダム値へ張り替える(例:crypto.randomBytes(48).toString('base64url'))。- 同時に
LEGACYへ各端末が今持っている旧値を入れる。これが移行期間の唯一の生命線。 - 再デプロイ(環境変数はデプロイ時にスナップショットされる基盤が多い。変更しただけでは効かない)。
- 各端末が「旧値で 200 → 新 primary を受領して上書き」まで進んだことを実測する。
- 確認できてから
LEGACYを削除して再デプロイ。使い捨てのENROLLもここで消す。
実測の仕方(ここを省くと直ったつもりで終わる)
- 端末側: 配布クライアントのログに
refreshed: <秘密ファイル>が出ているか。 - サーバ側: ランタイムログに
auth via legacyが残っていないか。 - 疎通テスト(値を出力しないスクリプトで): 新 primary=200 / 旧値(legacy)=200 / でたらめな秘密=401 の3点を必ず確認する。 「200 が返った」だけでは不十分で、認証が効いていること(401 側) まで見る。
4. 環境変数の落とし穴
- 暗号化された環境変数は CLI で読み戻せないことがある(値が空で返る)。
「既存値を読んで merge して置き換える」は成立しない。新しい変数名を足す設計にする
(
KEYS_JSON,KEYS_JSON_EXTRA,KEYS_JSON_EXTRA2… をサーバ側で全部 merge する)。 読み戻せると誤解したままrm → addすると、配布中の鍵一式を丸ごと失う。 - 削除→追加の間に失敗すると
primaryが消えた状態になる。新しい値は必ず先にローカルへ書いてから張り替える (復旧できる状態を作ってから壊す)。 - 一時トークンは「発行 → 渡す → 使わせる → 消す」までを1セットにする。消し忘れると、 チャット履歴や作業ログに平文で残った1本で全社のキー一式が引ける状態が続く。
5. AI に作業させるときの指示テンプレ
目的: <サービス名> の共有秘密をローテーションする。端末を1台も締め出さないこと。
前提として先に調べて報告すること:
- 配布クライアントは新しい秘密を「上書き」で受け取れる実装になっているか(既存ならスキップになっていないか)
- 旧秘密を残す LEGACY 相当の受け口がサーバ側にあるか
- 現在どの端末が認証できているか(ログの 401 を実測する)
そのうえで ①クライアント修正 → ②新値へ張り替え+旧値を LEGACY へ → ③再デプロイ →
④新primary=200 / legacy=200 / でたらめ=401 を実測 → ⑤移行確認後に LEGACY と一時トークンを削除、の順で行う。
秘密値は出力しない(指紋 sha256 の先頭8桁と HTTP status だけ報告する)。
- 秘密を扱うスクリプトは 値を print しない設計にし、確認は「指紋・件数・status」で行う。
- 一時ファイルに書いた秘密は作業後に削除する。
6. チェックリスト
- サーバは primary / legacy / enroll の複数本を受け付け、一致ラベルだけをログに出す
- 応答に「現行 primary 入りのファイル」を必ず含める(端末が自動で正規化される)
- クライアントは秘密ファイルだけ上書き更新、他は既存スキップ
- 失敗(401)は人の見る通知先へ1行、24時間抑止つき、秘密値なし
- ローテーションは「クライアント修正 → 張り替え+LEGACY → 再デプロイ → 実測 → LEGACY 削除」の順
- 暗号化 env は読み戻せない前提で、追加は新しい変数名で行う
- 一時トークンは削除まで完了して初めて「閉じた」と言う
よくある質問
+「共有秘密をローテーションしても端末を締め出さない鍵配布サーバ設計」とは何ですか?
全PCへAPIキーを自動配布する小さなサーバで、共有秘密の張り替え時に全端末が永久401になる事故を防ぐ設計と復旧手順。primary/legacy/enrollの3層認証、クライアント側の上書き更新分岐、ローテーションの正しい順序と実測方法
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約4.2万トークンかかりますが、この巻物を使えば約6,000トークンで済みます。差し引き約3.6万トークン(API料金換算で約54円)・86%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
Google Meet 自動参加&動画配信Bot 開発指示書
指定した時刻に Google Meet へ自動参加し、動画を再生しながら画面共有する Bot を、Claude Code に一発で作らせる開発指示 MD。朝会の定例動画配信・ウェビナーの自動放送に。
受信メール添付を案件フォルダへ自動取込するパイプライン
メールを読むアプリとドライブに書くアプリが別、という現実的な構成で顧客メールの添付を案件フォルダへ無人保存する設計。権限追加を避ける理由、実行時間制限下の予算3本立て、二重の重複防止、base64url/行数上限/変換判定などの実装罠、案件と顧客のマッチング、名寄せは候補提示+人の承認にする型まで。
Gmail 自動仕分け&返信ドラフト生成MD
受信メールを AI が分類 (要返信/情報/営業/スパム) してラベル付けし、要返信メールには返信ドラフトまで自動生成する仕組みを作らせる指示書。DWD (ドメイン全体委任) 設定手順込み。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア