Ubuntu - TECH PLAY - TECH PLAY

TECH PLAY

Ubuntu

イベント

マガジン

該当するコンテンツが見つかりませんでした

技術ブログ

1. はじめに 2026年8月5日、Kiro Crewが公開されました。 https://kiro.dev/crew/ Kiro Crewは、Kiro CLIを実行基盤として、自分のPCやサーバー上で動かせるオープンソースの開発ワークスペースです。 公式ページでは、セッションをまたぐMemory、定期実行、長時間タスク、複数エージェントの並列実行、Webダッシュボードなどが紹介されています。Kiro IDEやKiro CLIを置き換えるというより、Kiro CLIと既存の.kiro設定を使いながら、作業をセッションの外まで広げるプロダクトという位置付けです。 今回はEC2上のUbun
はじめに こんにちは!サイオステクノロジーのなーがです。最近は Claude Code の Skill や Subagent を育てるのがすっかり日課になっていて、気づけば .claude/ 配下のファイルがかなりの数に膨れ上がってきました。 ただ、増えてくると困るのが 設定の記述ミス です。SKILL.md のフロントマターのキーを typo した、Skill の説明文からリンクしていたファイルをリネームして参照切れになった、 settings.json の hooks でイベント名を間違えた……。 こうしたミスの厄介なところは、 実行するまで気付けない ことです。しかも Claude Code は壊れた設定をエラーで教えてくれるとは限らず、 該当の Skill や Hook を黙って無視する ことがあります。「あれ、この Skill 最近発動してなくない?」と気付いた頃には、いつのコミットで壊れたのか分からない……なんてことも。 これはもう Linter の出番だなということで、 .claude/ 配下をコミット前に静的検証する agentlint という Linter を自作しました。今回はそのご紹介です。 agentlint とは agentlint は、Claude Code のエージェント設定( .claude/ 配下の skills / commands / agents / hooks 設定)を検証する Python 製の Linter です。フロントマターの記述ミス、壊れたファイル参照、hooks 設定の構造ミスをコミット前に静的検出し、pre-commit や CI に組み込めるようにしています。 検証対象のファイルは以下の通りです。 your-project/ ├── CLAUDE.md # 参照切れ検出 └── .claude/ ├── skills/**/SKILL.md # フロントマター検証 + 参照切れ検出 ├── commands/**/*.md # フロントマター検証 + 参照切れ検出 ├── agents/*.md # フロントマター検証 + 参照切れ検出 + 再委譲禁止記述の検証 ├── settings.json # hooks設定検証 └── settings.local.json # hooks設定検証 4つのチェック チェック内容は大きく4つに分かれています。 チェック 対象 概要 1. フロントマター検証 .claude/skills/**/SKILL.md 、 .claude/commands/**/*.md 、 .claude/agents/*.md YAML フロントマターの構文・必須キー・列挙値・型を検証 2. 参照切れ検出 上記 Markdown 本文 + ルートの CLAUDE.md $CLAUDE_PROJECT_DIR / $CLAUDE_SKILL_DIR / .claude/ 起点の参照、および本文中の相対パス参照の実在確認(すべて warning) 3. hooks 設定検証 .claude/settings.json 、 .claude/settings.local.json トップレベル hooks の構造・イベント名・matcher・handler、および command が参照するスクリプトの実在(AL305)を検証 4. サブエージェント再委譲禁止の検証 .claude/agents/*.md 本文に「他のサブエージェントを呼び出さない」等の再委譲禁止の記述があるかを検証(AL401、回帰防止) 4つ目だけ少し毛色が違いますが、これは私のプロジェクトで「サブエージェントがさらに別のサブエージェントを呼び出す多段リレーを禁止し、その旨を各エージェント定義の本文にも明記する」という運用をしているため、その記述が抜け落ちたときに警告してくれる回帰防止用のチェックです。この多段リレーを hooks で機械的に防ぐ話は 別の記事 にまとめているので、AL401 の背景が気になる方はあわせてどうぞ。なお、検証対象の一つである Agent Skills の仕組みそのものについては、 弊社メンバーのブログ記事 で紹介しているので、あわせて読んでいただけると理解が深まると思います。 なお、フロントマターの有効値リスト(イベント名、 model / effort / permissionMode の列挙値など)は、公式ドキュメントを出典としてデータ専用のモジュール( src/agentlint/spec.py )に切り出してあり、仕様変更時はこのファイルだけを更新すればよい作りにしています。 参照: Claude Code Hooks – 公式ドキュメント finding コード一覧 検出結果(finding)にはコードを振っています。AL1xx がフロントマター、AL2xx が参照・ファイルシステム、AL3xx が settings/hooks、AL4xx が運用ルール系です。 コード 重大度 内容 AL001 warning agentlint 自身の内部エラー(ツールのバグでコミットをブロックしないための最終防波堤) AL101 error フロントマターの YAML がパース不能 AL102 error 必須キー欠落(agent の name / description) AL103 error 値が無効(model / effort / permissionMode 等の列挙違反、または列挙キーの値が文字列でない) AL104 warning 未知のキー(「もしかして」候補付き) AL105 error 型違反(bool / str / list / int 等、フロントマターのキーが文字列でない場合も含む) AL106 warning description 欠落(SKILL.md のみ。commands では任意のため対象外)、または description + when_to_use 合計が1536文字超 AL201 warning アンカー付きパス参照切れ(Markdown 本文中) AL202 warning 相対パス参照が見つからない(Markdown 本文中) AL203 warning hooks が直接実行するスクリプトに実行権限がない AL301 error settings JSON がパース不能 AL302 error 存在しないイベント名(「もしかして」候補付き) AL303 error matcher の正規表現が不正 AL304 error 構造違反(配列でない、type が未知等) AL305 error hooks の command が参照するスクリプトが存在しない(スクリプトパスと確信できるトークンのみ判定対象) AL306 warning matcher 非対応イベントへの matcher 指定、未知キー AL401 warning エージェント定義本文に再委譲禁止(「他のサブエージェントを呼び出さない」等)の記述がない 出力形式 出力は ruff 風の1行1finding形式です。 ファイルパス:行番号: コード [重大度] メッセージ の並びで、最後にサマリー行が付きます。 .claude/agents/foo.md:3: AL103 [error] 'model' の値が無効: 'gpt-4' agentlint: 1 error(s), 0 warning(s) 問題がなければこうなります。 agentlint: ok (12 files checked) 設計思想: 誤検知ゼロを最優先 このツールを作るうえで一番こだわったのが、 誤検知(false positive)を出さない ことです。 pre-commit に組み込む Linter は、誤検知が1件でも起きると「またこれか」とチーム内で無効化・放置されてしまい、それ以降の見逃しの方が遥かに高コストになります。そこで agentlint では 「error にするなら warning 以上に保守的に。迷ったら検出しない」 を設計原則にしました。 error を2種類に限定した理由 コミットをブロックする error は、次の2種類だけに限定しています。 フロントマター / settings の構文・構造エラー (AL101 / AL102 / AL103 / AL105 / AL301 / AL302 / AL303 / AL304): YAML や JSON としてそもそも壊れている、必須キーがない、列挙値が無効、型が違う、など 機械的に白黒つけられるもの hooks の command が参照するスクリプトの実在確認 (AL305): 「スクリプトパスだと確信できるトークン」だけに絞った実在確認 一方で、Markdown 本文中の参照切れ(AL201 / AL202)は 常に warning です。Skill や Agent の説明文には .claude/skills/my-skill/SKILL.md のような 例示パス が頻出し、プレースホルダ判定だけでは実在するパスと原理的に区別できないためです。本文中の参照切れでコミットを直接ブロックすることはしません。 未知のキー(AL104)も同様に warning に留めています。公式ドキュメントの更新で新しいキーが追加されたとき、agentlint 側の追従が遅れると誤検知になってしまうためです。 参照切れ検出そのものも「迷ったら検出しない」方針で、プレースホルダらしき文字列( path/to 、 example 、 your- 、 my- を含む等)や、絶対パス、URL(スキーム付き・裸ドメインの両方)、ワイルドカードを含むトークンは対象外にしています。 AL305 のスクリプトパス判定の工夫 error に昇格させた AL305(hooks のスクリプト実在確認)は、その分だけ判定を慎重にしています。というのも、hooks の command 文字列には「 / を含むけどパスではない」トークンが山ほど出てくるんですよね。例えば…… sed -i 's/foo/bar/g' — sed の置換パターン jq -r ".a/b" — jq のフィルタ rm -rf *.log — glob date +%Y/%m/%d — 日付フォーマット $HOME/... — 未解決のシェル変数 これらを素朴に「パスっぽいから実在確認しよう」とやると誤検知まみれになります。そこで agentlint では、 「 $CLAUDE_PROJECT_DIR 置換後、未解決の変数( $ )や glob( * ? {} )を含まず、 .sh / .py 等の既知の実行系拡張子で終わる」トークンだけ を実在確認の対象にしています(この判定は src/agentlint/pathtokens.py に共通化しています)。 さらに、引数位置(2番目以降)のトークンは、先頭トークンがインタープリタ / ランナー( bash / python / uv / node 等)の場合のみ対象にしています。これは cp src.sh dst.sh の宛先のような「実行対象ではない引数パス」を誤検知しないための対策です。 AL001: 自身のバグでコミットをブロックしない もうひとつの防波堤が AL001 です。agentlint 自身のバグで想定外の例外が起きた場合、そのファイルの検査は諦めて AL001 の warning として報告 し、他のファイルの検査は継続します。 Linter のバグでユーザーのコミットがブロックされるのは、体験として本当に最悪なんですよね。なので「ツールが壊れても error にはしない」を仕組みとして保証しています。チェック処理は1ファイル単位で例外を捕捉するラッパー越しに実行しているので、1ファイルで転んでも残りのファイルの検査結果はちゃんと出ます。 使い方 ここからは実際の使い方です。ローカル実行 → pre-commit → CI の順に組み込んでいきます。 インストールと実行 agentlint は GitHub で公開 しています。PyPI などのパッケージレジストリには出していないので、リポジトリを clone して uv 経由で実行する形になります。 git clone https://github.com/Shotaro-Yoshinaga-sti/agentlint cd agentlint && uv sync セットアップできたら、あとは検証したいプロジェクトを --root で指定して実行するだけです。 uv run agentlint # カレントディレクトリの .claude/ を検証 uv run agentlint --root ../other # 別ディレクトリを指定 uv run agentlint --strict # warningのみでもexit code 1にする uv run agentlint --version 例えば、こんな設定ミスを仕込んだサンプルの .claude/ を用意してみます。 agents/foo.md : 必須キーの name / description が欠落、 model: gpt-4 (無効な値)、再委譲禁止の記述なし skills/deploy/SKILL.md : description を descripton と typo、本文から存在しない ./checklist.md を参照 settings.json : hooks のイベント名を PreToolUses と typo、存在しないスクリプト .claude/hooks/check.sh を command で参照 これに対して実行すると、以下の出力になります(実際の実行結果です)。 uv run agentlint --root ../broken-example .claude/agents/foo.md:1: AL102 [error] 必須キー 'description' が欠落している .claude/agents/foo.md:1: AL102 [error] 必須キー 'name' が欠落している .claude/agents/foo.md:1: AL401 [warning] サブエージェントの再委譲禁止(「他のサブエージェントを呼び出さない」等)の記述が見当たらない .claude/agents/foo.md:2: AL103 [error] 'model' の値が無効: 'gpt-4' .claude/settings.json:3: AL302 [error] 未知のイベント名 'PreToolUses'(もしかして: PreToolUse) .claude/settings.json:3: AL305 [error] hooks が参照するスクリプトが存在しない: .claude/hooks/check.sh .claude/skills/deploy/SKILL.md:1: AL106 [warning] description が設定されていない .claude/skills/deploy/SKILL.md:3: AL104 [warning] 未知のキー 'descripton'(もしかして: description) .claude/skills/deploy/SKILL.md:6: AL202 [warning] 相対パス参照が見つからない: ./checklist.md agentlint: 5 error(s), 4 warning(s) typo には「もしかして」候補が付くので、修正もすぐ終わります。これが地味に嬉しいんですよね。 exit code は error があれば 1、warning のみなら 0 です。CI で warning も落としたい場合は --strict を付けると warning のみでも exit code 1 になります。 また、 .claude/ ディレクトリが存在しない場合は何もせず exit code 0 で終了します。モノレポの一部ディレクトリなど、対象外の場所で実行されても邪魔をしません。 agentlint: .claude ディレクトリが見つかりません(/path/to/other/.claude)。何もしません。 pre-commit への組み込み agentlint は pre-commit hook としての利用を想定していて、リポジトリに .pre-commit-hooks.yaml を同梱しています。 公開しているので、利用側の .pre-commit-config.yaml にリポジトリを直接指定できます。 # .pre-commit-config.yaml - repo: https://github.com/Shotaro-Yoshinaga-sti/agentlint rev: v0.2.0 hooks: - id: agentlint 手元で改造しながら試したいときは、 pre-commit try-repo でローカルのチェックアウトを直接指定するのが手軽です。 pre-commit try-repo ../agentlint agentlint --all-files hook 定義側で files: ^(\.claude/|CLAUDE\.md) を指定してあるので、 .claude/ 配下か CLAUDE.md に変更があったコミットのときだけ動きます。 参照: pre-commit 公式ドキュメント CI での利用 pre-commit をすり抜けたケース( --no-verify でのコミットなど)に備えて、CI でも同じ検証を回しておくと安心です。GitHub Actions なら以下のようなジョブになります。 uvx --from git+... で公開リポジトリから直接取得して実行するので、事前インストールは不要です。 # .github/workflows/agentlint.yml name: agentlint on: [push, pull_request] jobs: agentlint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 - name: Run agentlint run: uvx --from git+https://github.com/Shotaro-Yoshinaga-sti/agentlint agentlint --strict ローカルの pre-commit では error のみブロック、CI では --strict で warning も含めて検知、という使い分けもできます。 既知の制限と使う上での考慮点 万能ではないので、現時点の制限も正直に書いておきます。ただ、どれも「知っていれば運用でカバーできる」類のものなので、制限ごとに「では利用者側はどう考慮すればいいか」までセットで整理します。 有効値リストは手動メンテ src/agentlint/spec.py の有効値リスト(イベント名や model / effort / permissionMode の列挙値など)は、2026-07 時点の公式ドキュメント準拠です。Claude Code 側の仕様変更に自動追従はしないため、新しいイベント名やフロントマターのキーが追加されると、 spec.py を更新するまでは誤検知(や見逃し)が起こり得ます。 使う側の考慮点としては、まず 未知のキー(AL104)が warning 止まりなのは、まさにこの事態のための設計 だと知っておくことです。仕様変更の直後に AL104 が出てもコミットはブロックされません。 公式ドキュメントに載っている正しいキーに対して AL104 が出ているなら、それは agentlint 側の追従漏れなので、その finding は無視して大丈夫です(そして spec.py に1行足せば直ります)。運用としては「公式ドキュメントの更新に気付いたら spec.py をメンテする」を回すイメージですね。 matcher の検証は Python の re による近似 hooks の matcher は Claude Code 内部では JavaScript の正規表現として解釈されますが、agentlint は Python の re モジュールで近似検証しています。両者の構文はおおむね互換とはいえ差異はあるので、JS では有効なのに Python では不正、といったパターンで誤検知 / 見逃しがあり得ます。 なので、 AL303 の error が出たときは「即修正」ではなく、「実際に Claude Code 上でその hook が動くか」を先に確認する のがおすすめです。Claude Code 上で正しく動いているなら構文差異による誤検知の可能性が高いです。そのうえで、matcher をツール名の完全一致や Bash|Edit のような単純な alternation に寄せておくと、そもそもこの構文差異を踏まなくなります。 対象はプロジェクトスコープのみ agentlint が見るのはプロジェクトスコープの .claude/settings.json / settings.local.json だけで、ユーザースコープの ~/.claude/ は対象外です。つまり、個人環境の ~/.claude/ に置いた設定が壊れていても検出されません。 これは「リポジトリにコミットされるものをコミット前に検証する」というツールの性格上の割り切りです。裏を返すと、 チームで共有したい Skill / Agent / hooks はプロジェクトスコープ(リポジトリ内の .claude/ )に寄せる 運用が前提になります。 共有物をリポジトリ側に置いておけばすべて agentlint の検証対象に入りますし、個人設定の壊れは被害が本人で閉じるので、まずは共有物を守る、という優先順位です。 AL305 が見るのは「スクリプトパスと確信できるトークン」だけ 設計思想のところ で書いた通り、AL305 の実在確認は .sh / .bash / .py / .js / .mjs / .ts の既知拡張子で終わるトークンだけが対象です。バイナリや拡張子なしスクリプトを直接実行している場合は、実在しなくても検出されません。また、引数位置のパスは先頭トークンがインタープリタ / ランナーの場合だけ見るので、 find -exec 等の別コマンドに渡したスクリプトパスも見逃します。いずれも誤検知回避を優先した 意図的な制限 です。 裏を返せば、 hooks の command を「インタープリタ + 拡張子付きスクリプトパス」の形(例: bash .claude/hooks/check.sh )に寄せておくと、AL305 の検証の恩恵をフルに受けられる ということです。凝ったワンライナーを command に直書きするより、処理を .sh / .py に切り出してシンプルに呼ぶ——という、hooks の可読性の面でもどのみち好ましい書き方に倒すほど、Linter もよく効くようになります。 このほか細かい点として、 .claude/commands/*.md では $CLAUDE_SKILL_DIR アンカーの参照を検証しません(commands では未定義のため)。 さいごに 今回は、Claude Code の .claude/ 配下を静的検証する自作 Linter「agentlint」を紹介しました。ポイントを整理します。 .claude/ 配下の設定ミスは 実行するまで気付けず 、Claude Code は壊れた設定を黙って無視することがある agentlint は フロントマター検証・参照切れ検出・hooks 設定検証・再委譲禁止記述の検証 の4チェックをコミット前に静的実行する 設計原則は 「error にするなら保守的に。迷ったら検出しない」 。error は構文・構造エラーと AL305 に限定し、誤検知でツールが放置される事態を避ける pre-commit と CI に組み込めば、壊れた設定がリポジトリに入る前に検知できる Skill や Subagent が増えてくると、 .claude/ 配下は立派な「コード」です。コードなら Linter があって然るべき、ということで作ってみましたが、導入してからはフロントマターの typo やリネーム漏れをコミット前に何度も拾ってくれています。 みなさんも .claude/ が育ってきたら、設定の静的検証を仕組み化してみてはいかがでしょうか。この記事がその参考になれば嬉しいです! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Code の .claude/ 設定を静的検証する Linter「agentlint」を作った話 first appeared on SIOS Tech Lab .
はじめに こんにちは、情報セキュリティ部の 兵藤 です。日々ZOZOの安全を守るためSOC業務に取り組んでいます。 本記事では、開発環境におけるサプライチェーン対策として、Takumi Guardを全社展開した事例について紹介します。 また、情報セキュリティ部ではその他にもZOZOを守るための取り組みを行っています。詳細については以下の「Claude CodeがSOC業務を全自動でやってくれるってさ」をご覧ください。 techblog.zozo.com 目次 はじめに 目次 背景と概要 Takumi Guardとは Takumi Guardの対応エコシステム Takumi Guardの適用範囲 Takumi Guardの全社展開 コンテナでのTakumi Guard利用 MDMでのTakumi Guard配布 シークレットの問題 Takumi Guardの爆速アップデート対応 許可の設定をEntra側に寄せる シークレットの閲覧 デバイススクリプトの更新権限 Push Ruleset 展開後のログ画面 おわりに 背景と概要 昨今ではサプライチェーン攻撃の脅威が増しており、2026年3月には開発環境でよく使われる axios などが侵害されました。 このような事例から開発環境におけるセキュリティ対策の重要性が高まっています。特に、開発者が使用するツールやパッケージの安全性を確保することは、プロダクトセキュリティに直結します。 そこで、私たちは開発環境におけるサプライチェーン対策として、Takumi GuardをMDM経由で全社展開しました。 Takumi Guardとは Takumi Guard は、GMO Flatt Securityが提供する、悪意のあるパッケージの検知・ブロックを行うツールです。個人利用であれば無償ですが、組織として全端末に展開したり、ログを収集したりする場合は有償の契約が必要です。 詳しくは公式ドキュメント 1 をご覧ください。 Takumi Guardの対応エコシステム Takumi Guardはエンジニアが利用する開発言語における悪意のあるパッケージを検知・ブロックできます。2026年7月27日時点では、以下のエコシステムに対応しています。 npm PyPI RubyGems Go Packagist 2026年4月当初はまだ npm や PyPI までしか対応していませんでしたが、現在では RubyGems や Go 、 Packagist にも対応しており、開発速度が凄まじいです。 Takumi Guardの適用範囲 Takumi Guardの適用範囲は、大きく開発端末とCI/CD環境の2つに分けられます。 弊社ではGitHub ActionsをCI/CD環境として利用しています。このCI/CD環境にもTakumi Guardを導入でき、 公式ドキュメント に簡易手順が記載されているため導入は容易です。手順に記載されているBot IDを利用すると、GitHub Organizationに紐づくリポジトリへTakumi Guardを導入し、ログを後から確認できます。 BotはTakumi Guardのポータルから以下の項目で追加できます。 Botの追加 作成されたBotは「設定」の項目から確認可能です。 Botの確認 Takumi Guardの全社展開 Takumi Guardを全社展開するにあたり、弊社では以下の項目に対応しました。 開発ガイドラインへのTakumi Guardの記載 GitHub Actions環境へのTakumi Guardの導入 全社端末へのMDM経由でのTakumi Guardの導入 これらの対応の中で工夫した点について紹介します。 コンテナでのTakumi Guard利用 GitHub Actions上でコンテナを利用する場合、コンテナ内でのレジストリプロキシについては flatt-security/setup-takumi-guard-npm@v1 などのActionsだけでは対応できません。 これらのActionsの outputs で registry-url や token を取得し、コンテナ内部に渡す必要があります。 この点には注意が必要です。ビルド時に渡す場合、 --mount=type=secret を利用してコンテナ内部に token を渡します。そして、 npm install などを実行する際には同じ RUN 内で完結させる必要があります。別の RUN で実行すると、イメージのレイヤーに token が残ってしまうため、セキュリティ上の懸念があります。 DockerfileやGitHub Actionsのワークフローについては以下のようなものを参考として作成し、全社へ共有しました。 FROM node:20 WORKDIR /app COPY package.json package-lock.json ./ # secretマウントし、そのRUNの中だけで .npmrcを作って使い、最後にrm RUN --mount=type=secret,id=takumi_token \ printf 'registry=https://npm.flatt.tech/\n//npm.flatt.tech/:_authToken=%s\n' \ "$(cat /run/secrets/takumi_token)" > /tmp/.npmrc && \ export NPM_CONFIG_USERCONFIG=/tmp/.npmrc && \ npm ci && \ npm install some-extra-package && \ rm -f /tmp/.npmrc - name : Build image env : TAKUMI_TOKEN : ${{ steps.takumi.outputs.token }} run : | docker build \ --secret id=takumi_token,env=TAKUMI_TOKEN \ -t myapp . PyPIの場合は pip install を実行した際、 ~/.cache/pip にレスポンスの情報などが残ってしまいます。トークンがそのまま残るわけではありませんが、 --no-cache-dir を利用してキャッシュを残さないようにするのがベターです。 FROM python:3.13-alpine WORKDIR /app ... # Takumi Guardのトークン入りPIP_INDEX_URLをsecretとしてマウント RUN --mount=type=secret,id=pip_index_url \ export PIP_INDEX_URL="$(cat /run/secrets/pip_index_url)" && \ pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r requirements.txt - name : Build image run : docker build --secret id=pip_index_url,env=PIP_INDEX_URL -t myapp . 上記の渡し方は一例のため、開発環境に合わせて適宜変更してください。 MDMでのTakumi Guard配布 ZOZOではMDMとしてMicrosoft Intuneを利用しています。Intuneでは、Macについては シェルスクリプト を利用してTakumi Guardのラッパースクリプトを配布できます。Windowsについては 修復スクリプト を活用できます。検出スクリプトをダミーで作成し、修復スクリプトでTakumi Guardのラッパースクリプトを配布する形です。 これらのスクリプトはIntuneで定期実行しています。何らかの理由でTakumi Guardが外れてしまった場合でも、次回の定期実行時に再度配布されます。 Takumi Guardのラッパースクリプトについては 公式ドキュメント に記載されています。 これらのスクリプトを利用することで、全社端末にTakumi Guardを配布できます。 シークレットの問題 Intuneでシェルスクリプトを配布する場合、シークレットをうまく扱えないという問題があります。この点が今後の課題です。対策としては、SCEP方式の証明書を端末に配布した後、中間APIサーバを立ててAzure Key Vaultからシークレットを取得する方法などが考えられます。 この作業には各部署との調整や、ラッパースクリプトの大幅な改修が必要です。また、少数チームにとっては実装と運用のコストが高くなります。 このため、Takumi Guardの展開遅延の懸念がありました。昨今のサプライチェーン攻撃の脅威を考え、Takumi Guardのトークンを払い出すだけのBotシークレットの権限であれば一旦このリスクは許容し、速度を優先することにしました。 この方法を実施する場合は、定期的にBotのシークレットのローテーションを行うなど、リスク軽減を図る必要があります。 またトークンの異常な発行が行われていないか発行量を適宜確認し、必要に応じて対応することも求められます。この点はKey Vaultなどを利用した場合も同様です。 Takumi Guardの爆速アップデート対応 上記以外にも対応すべきことはありました。それは、爆速で行われるTakumi Guardのアップデートへの追従です。 前述の通りTakumi Guardは日々アップデートされています。展開の仕組みを整えている途中でRubyGems対応の機能が追加された時は、その速度感に驚愕したことを覚えています。 この速度に追従するためには、利用する側でも同様のデプロイの仕組みを整えておく必要がありました。ZOZOではGitHub Actions経由でIntuneのラッパースクリプトを更新する仕組みを整え、Takumi Guardの爆速アップデートへ追従できるようにしました。 以下がmacOSにおけるラッパースクリプトの更新のワークフローです。 name : Deploy Takumi Guard macOS script to Intune on : push : branches : - main paths : - scripts/install_takumiguard.sh permissions : contents : read id-token : write concurrency : group : intune-takumi-guard-mac cancel-in-progress : false jobs : deploy-macos-script : name : Deploy Takumi Guard macOS script runs-on : ubuntu-latest timeout-minutes : 30 steps : - name : Checkout uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with : persist-credentials : false - name : Azure Login uses : azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 with : client-id : ${{ vars.AZURE_CLIENT_ID }} tenant-id : ${{ vars.AZURE_TENANT_ID }} allow-no-subscriptions : true - name : Fetch TG Bot API key from Key Vault id : keyvault env : AZURE_KEY_VAULT_NAME : ${{ vars.AZURE_KEY_VAULT_NAME }} run : | set -euo pipefail : "${AZURE_KEY_VAULT_NAME:?Set AZURE_KEY_VAULT_NAME in GitHub Actions variables}" api_key="$(az keyvault secret show \ --vault-name "${AZURE_KEY_VAULT_NAME}" \ --name tg-bot-api-key-mac \ --query value \ -o tsv)" if [ -z "${api_key}" ] ; then echo "Failed to fetch tg-bot-api-key-mac from Key Vault ${AZURE_KEY_VAULT_NAME}" > &2 exit 1 fi echo "::add-mask::${api_key}" echo "api_key=${api_key}" >> "${GITHUB_OUTPUT}" - name : Resolve target Intune macOS script env : INTUNE_TAKUMI_MAC_SCRIPT_ID : ${{ vars.INTUNE_TAKUMI_MAC_SCRIPT_ID }} run : | set -euo pipefail : "${INTUNE_TAKUMI_MAC_SCRIPT_ID:?Set INTUNE_TAKUMI_MAC_SCRIPT_ID in GitHub Actions variables}" echo "TAKUMI_MAC_SCRIPT_ID=${INTUNE_TAKUMI_MAC_SCRIPT_ID}" >> "${GITHUB_ENV}" echo "TAKUMI_MAC_SCRIPT_RESOURCE=deviceManagement/deviceShellScripts" >> "${GITHUB_ENV}" - name : Update Intune macOS script shell : bash env : SCRIPT_PATH : scripts/install_takumiguard.sh TG_BOT_API_KEY_MAC : ${{ steps.keyvault.outputs.api_key }} run : | set -euo pipefail : "${TG_BOT_API_KEY_MAC:?Failed to resolve tg-bot-api-key-mac from Key Vault}" echo "::add-mask::${TG_BOT_API_KEY_MAC}" build_script_content() { local api_key_literal api_key_literal="$(printf '%s' "${TG_BOT_API_KEY_MAC}" | perl -0pe 's/([\\\"\$`])/\\$1/g' )" API_KEY_EXPORT_LINE="export TG_BOT_API_KEY=\"${api_key_literal}\"" awk ' /^export TG_BOT_API_KEY=/ { print ENVIRON["API_KEY_EXPORT_LINE"] replaced += 1 next } { print } END { if (replaced != 1) { printf("Expected one TG_BOT_API_KEY export line, found %d\n", replaced) > "/dev/stderr" exit 1 } } ' "${SCRIPT_PATH}" | base64 -w 0 } script_content="$(build_script_content)" access_token="$(az account get-access-token \ --resource https://graph.microsoft.com \ --query accessToken \ -o tsv)" echo "::add-mask::${access_token}" response="$(printf '%s' "${script_content}" \ | jq -Rnc '{scriptContent: input}' \ | curl -sS -w " \n %{http_code}" \ -X PATCH "https://graph.microsoft.com/beta/${TAKUMI_MAC_SCRIPT_RESOURCE}/${TAKUMI_MAC_SCRIPT_ID}" \ -H "Authorization: Bearer ${access_token}" \ -H "Content-Type: application/json" \ --data-binary @-)" http_status="${response## *$'\n' } " if [ "${http_status}" = "200" ] || [ "${http_status}" = "204" ] ; then echo "Updated Takumi Guard Intune macOS script (${http_status})" else echo "Failed to update Takumi Guard Intune macOS script (${http_status})" echo "Response body omitted to avoid leaking scriptContent." exit 1 fi - name : Verify Intune macOS script update shell : bash env : SCRIPT_PATH : scripts/install_takumiguard.sh TG_BOT_API_KEY_MAC : ${{ steps.keyvault.outputs.api_key }} run : | set -euo pipefail : "${TG_BOT_API_KEY_MAC:?Failed to resolve tg-bot-api-key-mac from Key Vault}" echo "::add-mask::${TG_BOT_API_KEY_MAC}" build_script_content() { local api_key_literal api_key_literal="$(printf '%s' "${TG_BOT_API_KEY_MAC}" | perl -0pe 's/([\\\"\$`])/\\$1/g' )" API_KEY_EXPORT_LINE="export TG_BOT_API_KEY=\"${api_key_literal}\"" awk ' /^export TG_BOT_API_KEY=/ { print ENVIRON["API_KEY_EXPORT_LINE"] replaced += 1 next } { print } END { if (replaced != 1) { printf("Expected one TG_BOT_API_KEY export line, found %d\n", replaced) > "/dev/stderr" exit 1 } } ' "${SCRIPT_PATH}" | base64 -w 0 } access_token="$(az account get-access-token \ --resource https://graph.microsoft.com \ --query accessToken \ -o tsv)" echo "::add-mask::${access_token}" expected_script_content="$(build_script_content)" actual_script_content="$(curl -sS \ "https://graph.microsoft.com/beta/${TAKUMI_MAC_SCRIPT_RESOURCE}/${TAKUMI_MAC_SCRIPT_ID}" \ -H "Authorization: Bearer ${access_token}" \ -H "Accept: application/json" \ | jq -r '.scriptContent // empty' )" if [ "${actual_script_content}" != "${expected_script_content}" ] ; then echo "Takumi Guard macOS scriptContent verification failed" > &2 exit 1 fi echo "Verified Takumi Guard Intune macOS scriptContent" ポイントがいくつかあるため、以下の章で解説します。 許可の設定をEntra側に寄せる GitHub Actionsのワークフローでは Azure Login のOIDCでEntraのサービスアカウントにログインします。固定のシークレットを極力持たせたくないため、この構成を採用しました。 シークレットの閲覧 シークレット情報はGitHub Organizationの権限で管理するのではなく、EntraやARM側に寄せました。GitHub ActionsのワークフローからはEntraのサービスアカウント経由でAzure Key Vaultからシークレットを取得する形です。 取得のために Key Vault Secrets User のRoleを対象のKey Vaultに絞って、このサービスアカウントへ付与する必要があります。 Actions上ではメモリ上でシークレットを扱い、ログに出力されないように ::add-mask:: を利用してマスクしています。 ただ、前述のとおりIntune上ではシークレットが残ってしまいますが、この閲覧権限もIntuneの権限を付与できるEntraで管理できるので、Entraでの権限設定が重要です。 デバイススクリプトの更新権限 この権限もEntra側のサービスアカウントに付与します。 Microsoft Graph APIの deviceManagement/deviceShellScripts のエンドポイントを呼び出す必要があります。これは強い権限のため、上記ワークフローを実施するスクリプトの main ブランチへの変更を厳しく確認する必要がありました。 Push Ruleset そもそも、このリポジトリを閲覧できるアカウントを絞ることが前提です。加えて、そのアカウントによるPushにも制限が必要です。 このActionsは、 main ブランチへのpushかつ scripts/install_takumiguard.sh に変更があった場合のみ実行されるようにしています。 この変更についてはGitHubの Rulesets で管理しています。以下の項目を設定しておくとPRでレビューの通ったものがマージされるようになります。 Restrict deletions Require a pull request before merging Block force pushes 基本的にこのRuleのバイパスは設定せず、一律PRのレビューを通すようにしています。これにより、リポジトリの変更がある場合は必ずレビューが入るようになります。 レビュー者は .github/CODEOWNERS で設定しておくと、PR作成時に自動でレビュー依頼が飛びます。 展開後のログ画面 パッケージのダウンロードログはTakumiのポータルから確認できます。ブロックなどの条件で絞り込むことで、どのパッケージがブロックされたかを簡単に確認できます。 展開後のログ画面 おわりに 本記事では、開発環境におけるサプライチェーン対策として、Takumi Guardを全社展開した事例について紹介しました。 まだまだ課題はありますが、Takumi Guardの導入により、開発環境におけるサプライチェーン攻撃のリスクを低減しました。 ZOZOでは、一緒に安全なサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください! hrmos.co Takumi Guard ↩

動画

該当するコンテンツが見つかりませんでした

書籍