Claude Code・Codex などのコーディングエージェントに az コマンドを触らせると、調査も運用も一気に速くなります。「この resource group の VM を一覧して」「失敗している Function App の直近ログを引いて」と頼めば、エージェントが自分で CLI を組み立てて結果を返してくれる。便利さは疑いようがありません。
問題は、そこでエージェントに渡しているのが「シェル」と「Azure の認証情報」の両方だという点です。シェルから az を叩ける状態は、裏を返せば、エージェントが読んだ README や Issue、Web ページ、ツール出力に紛れ込んだ指示が、そのままクラウド操作の実行に化けうる状態でもあります。後述するとおり、これは理論上の懸念ではなく、すでに実害の出ている攻撃クラスです。
本記事では、Azure CLI(az)を AI エージェントに触らせる構成を題材に、Azure 側の強制境界で守る多層防御を組み立てます。結論を先に置くと、短期クレデンシャルと最小権限という Microsoft 公式の方向性は正しい。ただしサービス プリンシパルのシークレットは漏えい時の持続性が高く、エージェント側の権限設定は敵対的な回避には耐えません。唯一の信頼できる強制境界は RBAC・組織ポリシー・OS サンドボックスである、という前提に立って設計します。
なお、本記事は AWS 版「AI エージェント経由で AWS CLI を安全に使う」・Google Cloud 版「AI エージェント経由で Google Cloud CLI を安全に使う」の Azure 版にあたります。考え方の骨格は共通ですが、Azure には Managed Identity による secretless 認証、CanNotDelete リソースロックというエージェントから独立したバックストップ、コントロール/データ/ID/エージェントの監査四層など固有の武器があり、設計の作り込みは別物になります。
Microsoft の公式ガイダンスは答えの骨格を示している
Azure は AI エージェントに az を使わせる前提に立たなくても、認証と認可の原則をすでに明確に示しています。設計の出発点として、まずその原則を押さえます。
- 認証はシークレットを使わない: サービス プリンシパルのクライアント シークレットの常用を避け、Azure 内なら Managed Identity、Azure 外なら Workload Identity Federation で毎回短期トークンを生成する。
- 主体を分離する: エージェント専用の ID を与え、人間の広い権限と切り離して最小権限で動かす。
- 入力を信頼しない: 取得した文書・コマンド出力に埋め込まれた指示でエージェントが乗っ取られる前提で、影響範囲を縛る。
逆に言えば、足りないものもはっきりしています。シークレットベースの常駐認証、自由形式のシェル実行、広いネットワーク外向き許可、共有 ID、広いスコープの Contributor/Owner、未有効のデータプレーン監査ログ、~/.azure に残る平文トークンキャッシュ。これらを一枚岩で塞ぐ銀の弾丸はありません。だから、層を重ねます。
多層防御で「権限ひとつ」を無効化する
設計を貫く原則は多層防御(defense in depth)です。狙いは、エージェントが想定外に動いても、それだけでは本番に届かない状態を作ること。つまり「エージェントは何をする必要があるか」ではなく「想定外に動いたとき、影響範囲はどこまで広がるか」で権限を決める、という発想です。
具体的には、認証・権限・実行・承認・監査の五層で守ります。
flowchart TD
A["AI エージェントが az コマンドを生成"] --> B["第 1 層: 認証 — Managed Identity・WIF と専用環境"]
B --> C["第 2 層: 権限 — RBAC 最小権限・カスタムロール・PIM"]
C --> D["第 3 層: 実行 — シェルを介さないコマンドブローカー"]
D --> E["第 4 層: 承認 — 人手承認・CanNotDelete リソースロック"]
E --> F["第 5 層: 監査 — Activity Log・Resource Logs・Entra・Caller"]
F --> G{"すべての関門を通過?"}
G -- "いいえ" --> H["拒否・記録・通知"]
G -- "はい" --> I["Azure API / Key Vault 実行"]
ここで肝心なのは層の強さに序列があることです。RBAC・組織ポリシー・OS サンドボックスだけが敵対的な回避に耐える強制境界であり、エージェント側の permission や denylist は「ヒューマンエラーと低レベルの誤実行」を減らす利便性レイヤーにすぎません。後者を主役に据えると設計を読み違えます。さらに Azure には、ユーザー権限すら上書きするリソースロックという、エージェントの暴走から独立して効くバックストップがあります。以下、層ごとに作り込んでいきます。
シークレットを捨てる — Managed Identity・Workload Identity Federation
最初の層は認証です。ここでの最重要原則は単純で、長期のシークレットを使わないことです。
Azure CLI の認証は 2.30.0 以降 MSAL ベースになり、対話型のブラウザ/デバイスコードログイン、サービス プリンシパル、Managed Identity、Workload Identity Federation を扱えます。AI エージェント用途では、この選択肢に明確な序列があります。
- Managed Identity: エージェントが Azure コンピュート(VM・Container Apps・AKS など)で動くなら第一選択。
az login --identityでログインでき、Azure がクレデンシャルを管理・ローテーションするため、盗まれるシークレットがそもそも存在せず、その Azure リソース外では使えない。Managed Identity は secretless 認証の基本線です。 - Workload Identity Federation: CI/CD(GitHub Actions)、Kubernetes、他クラウド、オンプレなど Azure 外で動くエージェント向け。外部 IdP が発行した短命の OIDC トークンを Entra トークンへ交換する方式で、保存するシークレットがない。Workload Identity Federation は漏えいと期限切れの両リスクを同時に下げます。
- 証明書ベースのサービス プリンシパル: 上の二つがどうしても使えない場合のフォールバック。Microsoft は password-based より certificate-based 認証を推奨しており、証明書は Key Vault に置き、リポジトリや
.envに PEM を置かないのが鉄則です。 - ユーザー資格情報: 自律エージェントには絶対に渡さない。エージェントが人間の標準権限をそのまま継承し、トークンが
~/.azureに残ります。2025 年 10 月 1 日に Azure Resource Manager 層での MFA 強制が Phase 2 に入ったこともあり、ユーザー ID に依存する自動化はそもそも中断しやすくなっています。
ここで重要な注意があります。PIM(Privileged Identity Management)の JIT 昇格は、ユーザーとグループには効きますが、サービス プリンシパルや Managed Identity のようなワークロード ID には「eligible」割り当てを設定できません。つまりエージェント自身の ID については、PIM による都度昇格ではなく、最初から狭い常時スコープで縛るのが正解です。PIM は、エージェントを管理する人間側の特権昇格に使います。
そして、トークンキャッシュの扱いには OS 依存の落とし穴があります。MSAL はトークンを ~/.azure(msal_token_cache.bin など)にキャッシュしますが、Windows では DPAPI で暗号化される一方、Linux と macOS では平文ファイルとして保存されます。Azure CLI はバックグラウンドでトークンを黙って更新するため、このキャッシュを読めるプロセスは、生きた自動更新クレデンシャルをそのまま持ち出せます。OS が未指定の現場では、常に分離・短命・削除を前提に設計するのが安全で、具体策は後述のサンドボックスの節でまとめます。
最小権限とガードレール — RBAC・カスタムロール・PIM
第 2 層は権限です。ここが唯一の信頼できる強制境界の一つになります。
まず専用 ID を与えます。エージェント駆動の操作を人間の ID と分け、監査ログで操作をエージェントに帰属させ、権限を独立に絞れるようにするためです。エージェント 1 体につき 1 つの ID を割り当てると、後述する Activity Log の Caller 一致がそのまま追跡可能性につながります。
基本ロールは絶対に避けます。Owner や User Access Administrator、サブスクリプション単位の Contributor をエージェント ID に与えてはいけません。Azure RBAC のベストプラクティスは、所有者数の制限、広いスコープの回避、グループ経由の割り当て、ロール名ではなくロール ID の利用、カスタムロールでのワイルドカード回避を勧めています。エージェントには、必要なアクションだけを含むカスタムロールを、非本番の resource group へ最も狭くバインドします。
RBAC 割り当ては、ロール名ではなくロール ID を使い、サービス プリンシパルや Managed Identity には application ID ではなく object ID を使うのが安定です。新規作成直後の ID に割り当てるなら、レプリケーション遅延対策として --assignee-principal-type を付けます。
az role assignment create \
--assignee-object-id "$AGENT_OBJECT_ID" \
--assignee-principal-type ServicePrincipal \
--role "acdd72a7-3385-48ef-bd42-f606fba81ae7" \
--scope "/subscriptions/${SUB_ID}/resourceGroups/${RG_READONLY}"ここでロール割り当てそのものをエージェントに許す場合でも、Owner や User Access Administrator を直接与える代わりに、どのロールを・誰に・どのスコープで配れるかを限定した委任に絞ります。AI エージェントは人間より再試行・大量実行が容易なので、最小権限の原則はむしろ厳しく適用すべきです。
そして秘密管理は、Key Vault を「秘密をまとめて置く箱」ではなく、アプリ・リージョン・環境ごとに分離したセキュリティ境界として扱います。Microsoft はボールトを用途ごとに分離して blast radius を下げることを勧めています。アクセス制御は今後 Azure RBAC を中心に考えるべきで、2026-02-01 以降の API で作成する新規ボールトの既定アクセス制御モデルは Azure RBAC になりました。あわせて、Public access を無効にして Private Endpoint を優先し、ソフト削除と消去保護を有効化して、誤削除と妨害削除の両方から回復できるようにしておきます。
AI エージェント特有のリスク — プロンプトインジェクション
なぜここまで権限を絞るのか。AI エージェントに az を渡すことの本質的なリスクは、自然言語入力・取得文書・ツール出力が、そのままコマンド実行権限に結びつく点にあります。Microsoft は、agentic AI ではデータと制御の境界が曖昧になり、ツール・メモリ・他エージェントの導入で blast radius が広がると整理しています。OWASP も Prompt Injection を LLM リスクの第 1 位に置き、未承認のツール実行・情報流出・永続的な操作誘導につながる主要リスクと位置づけ、LLM の動作原理上、完全な防止策は存在しないと明記しています。
特に厄介なのが間接プロンプトインジェクションです。攻撃入力はユーザーの直接プロンプトに限りません。リソースのタグ説明、Storage 上のドキュメント、PDF、メール、外部 Web ページのような「ただの業務データ」に紛れ込みます。エージェントが「このタスクを達成するには新しいシークレットを作れ」「監査を避けるためログを削除しろ」といった埋め込み指示をデータではなく命令として解釈し、az role assignment create や az ad sp credential reset、データコピー系操作を実行すると、権限昇格や情報流出に発展します。
これは机上の話ではありません。2025 年 7 月、SaaStr 創業者 Jason Lemkin による複数日の「vibe coding」実験のさなか、Replit の AI エージェントがコードフリーズ中に本番データベースを削除した事故が報告されました。エージェントは繰り返しの指示を無視し、当初はロールバック不能だと誤報告し、約 4,000 件の偽ユーザーレコードを捏造していたとも伝えられています。Replit の CEO は「容認できない」と述べ、開発/本番の分離やロールバック改善、計画専用モードを追加しました。教訓は Azure にそのまま写せます。環境を分離し、破壊操作を人手承認なしに実行させず、エージェントの認証情報を本番に届かないようスコープすること。Anthropic も自社の「エージェントの誤挙動」内部インシデントログで、本番データベースに対するマイグレーション試行や認証トークンの持ち出しを記録しており、これらが「ユーザーの意図を超えた過剰な主体性」から起きたと整理しています。だから取得コンテンツは untrusted として扱い、最小権限と承認境界で「万一従ってしまっても影響が出ない」状態を作っておく必要があります。
エージェント側ガードレールは利便性レイヤー(強制境界ではない)
エージェント側のツールにも、もちろん安全機構はあります。ただし、その位置づけを正しく理解しておくことが重要です。
Claude Code はデフォルトで厳格な読み取り専用権限を採用し、ls・cat・git status などは確認なしで動く一方、システムを変更しうる Bash コマンドは承認を要求します。settings.json の permissions で allow・ask・deny を設定でき、評価順は deny → ask → allow、deny は他のどの allow よりも優先され、bypassPermissions モードでも効きます。プロジェクトの .claude/settings.json の例です。
{
"permissions": {
"defaultMode": "default",
"allow": [
"Bash(az account show)",
"Bash(az group list:*)",
"Bash(az resource list:*)",
"Bash(az vm list:*)"
],
"ask": [
"Bash(az group create:*)",
"Bash(az * update:*)"
],
"deny": [
"Bash(az group delete:*)",
"Bash(az * delete:*)",
"Bash(az * purge:*)",
"Bash(az role assignment *)",
"Bash(az ad sp *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(~/.azure/**)"
],
"disableBypassPermissionsMode": "disable"
}
}組織レベルでは managed-settings.json で上書き不可の deny を配り、permissions.disableBypassPermissionsMode を "disable" にして --dangerously-skip-permissions の使用そのものを禁止できます。Codex CLI も ~/.codex/config.toml の approval_policy(untrusted/on-request/never)と sandbox_mode(read-only/workspace-write/danger-full-access)で制御でき、.rules(Starlark)と execpolicy でコマンド単位の allow/prompt/block を判定し、組織は requirements.toml で never や danger-full-access を禁止できます。推奨ベースラインは approval_policy="on-request" + sandbox_mode="workspace-write" で、az はネットワークを要するため、ドメイン許可リスト付きプロキシ経由でのみ egress を開けます。
ただし、限界もはっきりしています。
- ファイル read の deny は万能ではない:
Read(~/.azure/**)のような deny は組み込みファイルツールと一部の bash ファイルコマンドにしか効かず、Python やシェルスクリプト、azのサブプロセスが間接的にファイルを開く経路は止まらない。OS レベルで全プロセスを止めるには sandbox が要る。 - glob マッチは回避されうる:
cd x && az … deleteのような複合コマンドや、ラッパー・変数経由の呼び出しで deny を抜けられる。フルのコマンド文字列を正規化して見るPreToolUsehook が信頼できる強制層になる。 - 設定が確実に効かないバグもある: permission が意図どおり効かない不具合報告があり、確実な強制には hook の併用が推奨される。
結論はこうです。エージェント側設定はヒューマンエラーと低レベルの誤実行を減らす利便性レイヤーであり、敵対的回避に対する強制境界は RBAC・組織ポリシー・OS サンドボックスである。だから次節からの実行レイヤーの作り込みが本丸になります。
なお、設定だけでは回避されうるので、破壊的コマンドは PreToolUse hook で確実に弾きます。hook は --dangerously-skip-permissions でもバイパスされず、exit 2 でツール呼び出しをブロックできます。
#!/usr/bin/env bash
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
DANGER='(az group delete|az .* delete|az .* purge|az vm deallocate|az role assignment (create|delete)|az ad sp credential|az lock delete)'
if echo "$CMD" | grep -qE "$DANGER" || echo "$CMD" | grep -qi 'prod'; then
echo "[danger-guard] BLOCKED: $CMD" >&2
exit 2 # exit 2 = ツール呼び出しをブロック
fi
exit 0シェルを介さないコマンドブローカー
第 3 層の実行レイヤーでは、コマンドインジェクションを根から断ちます。AI エージェントに az を直接自由実行させるのではなく、三層構造にします。LLM は意図だけを出す。ブローカーが許可判定し、構造化された az サブコマンドだけを組み立てる。サンドボックスで実行する。研究側も、制御フローを信頼入力だけから生成し、非信頼データがプログラムフローへ影響できない構造を有効策として示しており、ブローカー方式はこれに沿います。
flowchart TB R[ユーザー依頼] --> P[LLM プランナー] D[非信頼データ<br/>Web/PDF/メール/Storage/タグ] --> F[入力検証・サニタイズ] F --> P P --> Q[構造化アクション要求<br/>resource, action, scope] Q --> B[コマンドブローカー] B -->|許可されたコマンドのみ| X[実行サンドボックス] B -->|危険操作のみ| H[人手承認] H --> X X --> G[az] G --> A[Azure API / Key Vault] X --> L[Activity Log / Resource Logs / Entra Logs]
LLM が最終コマンドを直接所有しないことが肝心です。LLM はテンプレートと引数候補を出すだけに留め、最終的には allowlist ベースのバリデータが「実行してよい az サブコマンド」「許容フラグ」「許容値の文字種・長さ・スコープ」を機械的に判定します。たとえば {"resource":"vm","action":"list","resourceGroup":"rg-app"} のような内部表現から、決め打ちテンプレートで az vm list --resource-group ... を組み立てます。
具体的には、シェルを経由せず、許可された az サブコマンドだけを配列で実行するラッパーにします。OWASP は OS command injection の第一防御として「そもそも OS コマンドを直接呼ばない」ことを勧めており、az が必要ならこれが次善策です。
#!/usr/bin/env python3
import json
import subprocess
import sys
# (group, command) を allowlist で固定する。読み取り系のみ。
ALLOWED = {
("account", "show"),
("group", "list"),
("vm", "list"),
("resource", "list"),
("monitor", "activity-log"), # list は下位検証で絞る
}
PROFILE_SUBSCRIPTION = "SUBSCRIPTION_ID"
def main(argv: list[str]) -> int:
if len(argv) < 2:
print("usage: safe_az.py <group> <command> [args...]", file=sys.stderr)
return 2
key = (argv[0], argv[1])
if key not in ALLOWED:
print(f"blocked: {key} is not allowlisted", file=sys.stderr)
return 3
base = [
"az",
argv[0],
argv[1],
"--subscription", PROFILE_SUBSCRIPTION,
"--only-show-errors", # 警告ノイズを抑える
"--output", "json",
]
# shell=False によりシェルメタ文字の展開を避ける
cmd = base + argv[2:]
proc = subprocess.run(cmd, shell=False, capture_output=True, text=True)
if proc.returncode != 0:
# stderr はそのまま上位へ返さず、必要に応じてマスクする
print(json.dumps({"ok": False, "returncode": proc.returncode, "stderr": proc.stderr[:2000]}, ensure_ascii=False))
return proc.returncode
print(proc.stdout)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))ポイントは shell=False でメタ文字展開を避けること、group と command を allowlist で固定すること、--subscription を毎回ブローカーが付与すること、そして引数も別途検証することです。OWASP はコマンド名を許可リストで制御し、引数も正規表現などで検証することを勧めています。VM 名は ^[a-z][-a-z0-9]*$、resource group 名は [A-Za-z0-9_.()-]+ のように、CLI 固有の安全な語彙だけを通します。セミコロン・パイプ・リダイレクト・サブシェル展開・バッククォートといった shell 解釈に依存する表現は、ブローカー段階で禁止します。
CLI 出力の取り扱いも重要です。Azure CLI は 2.61 以降、機密情報出力に警告を出しますが、警告は STDERR に流れるため、failOnStderr を使う CI/CD ジョブを止めることがあります。本番ジョブでは az config set core.output=none を既定にし、必要な値だけ --query ... --output tsv で最小回収するのが現実的です。会話履歴やツールログに機密値が再挿入されると、通常の自動化より露出面が広くなる点も忘れないでください。
サンドボックスと egress 制御で隔離する
実行環境そのものも隔離します。Microsoft は Azure CLI の公式 Docker イメージ(Azure Linux ベース)を提供しており、決定論的タグの使用とベース OS の更新を勧めています。AI エージェント用ランナーとしては、1 ジョブ 1 コンテナ、永続ディスクなし、必要なデータのみ bind mount、SSH 鍵の無差別マウント禁止、外向き通信先の明示的制限、終了時破棄が妥当です。egress は *.azure.com・login.microsoftonline.com などの管理/ログインエンドポイントだけに絞ります。ファイルシステムの隔離だけでネットワークを開けたままにすると、乗っ取られたエージェントが SSH 鍵などを持ち出せてしまうためです。
Azure CLI のローカル状態も分離対象です。Microsoft は、同一ホストで Azure CLI を同時実行すると MSAL トークンキャッシュへの書き込み競合が起こり得るため、各ジョブで AZURE_CONFIG_DIR を個別ディレクトリに設定することを勧めています。前述のとおり Linux/macOS ではこのキャッシュが平文なので、ジョブごとに分離し、終了時に確実に消すのが必須です。エージェント専用の OS ユーザー・専用 HOME・専用 AZURE_CONFIG_DIR を用意し、人間の ~/.azure をエージェントに絶対に読ませない、というのが鉄則です。
Managed Identity で動かす最小構成は、ログイン・状態分離・出力抑制・痕跡消去を一つの流れにまとめます。
#!/usr/bin/env bash
set -euo pipefail
export AZURE_CONFIG_DIR="$(mktemp -d)"
trap 'az logout >/dev/null 2>&1 || true; az account clear >/dev/null 2>&1 || true; az cache purge >/dev/null 2>&1 || true; rm -rf "$AZURE_CONFIG_DIR"' EXIT
# 既定は出力抑制
az config set core.output=none
az config set core.only_show_errors=yes
# ユーザー割り当て Managed Identity でログイン
az login --identity --client-id "$UAMI_CLIENT_ID"
# 読み取り専用の安全な操作例
az vm list --resource-group "$RG_APP" \
--query "[].{name:name,location:location}" --output json高リスク操作は人手承認とリソースロックで縛る
プロンプトインジェクションを完全には防げない以上、高リスク操作には人間の承認を挟みます。破壊的な az パターンは、Claude Code では ask、Codex では承認プロンプトへ写像し、allowlist は読み取り系・冪等な操作だけに限定します。コマンドブローカー側でも、delete・purge・role assignment create/delete・ad sp credential reset・keyvault update --public-network-access・lock delete のような高インパクト操作は人手承認ゲートを必須にします。削除コマンドそのものの禁止だけでなく、「削除を可能にする事前条件の変更」も承認対象にする、というのが要点です。
そして Azure 固有の強力なバックストップが リソースロックです。本番の resource group やリソースに CanNotDelete ロックを付けておくと、たとえ Owner や暴走したエージェントであっても、まずロックを外さない限り削除できません。
az lock create \
--name "no-delete-prod" \
--lock-type CanNotDelete \
--resource-group "$RG_PROD"ロックはユーザー権限を上書きするため、エージェントの権限設計とは独立に効きます。ロック解除自体が監査・承認可能な操作になるので、エージェントには lock delete を禁止し、解除は人間の手続きに固定します。なお ReadOnly ロックは想定以上に広く効く(Storage キーの一覧取得や VM の起動/停止まで止める)ことがあるため、エージェント安全の目的では CanNotDelete を優先します。ARM/Bicep デプロイでは az deployment group what-if で差分を先に確認させ、適用前に人間がレビューするのも有効です。
監査と検知 — 四層監査と Caller
最後の層は、誤動作を事後に追える状態を作ることです。エージェントの暴走は、可視化なしでは収束できません。Azure では、コントロールプレーン・データプレーン・ID プレーン・エージェントプレーンの四層で監査を設計します。
- コントロールプレーン: Activity Log は構成の作成/更新/削除を自動で記録し、
Callerフィールドに操作した主体が残ります。エージェント 1 体に固有のサービス プリンシパル/Managed Identity を割り当てれば、Callerがそのまま帰属になります。既定保持は 90 日なので、診断設定で Log Analytics(AzureActivityテーブル)や不変ストレージへエクスポートして長期保持と KQL 相関を可能にします。 - データプレーン: Key Vault でのシークレット取得のような操作は Activity Log には出ず、Resource Logs(Key Vault なら
AuditEventカテゴリ)に出ます。エージェントが「何を読んだか」を追うには、これを明示的に有効化する必要があります。 - ID プレーン: Microsoft Entra のサインインログ(
AADServicePrincipalSignInLogsテーブルが非対話のサービス プリンシパル サインインを記録)と監査ログで、ID 側の動きを追います。 - エージェントプレーン: エージェント自身のプロンプト、計画、承認、実行結果、STDERR/STDOUT をアプリ側ログとして残します。Claude Code・Codex はいずれも OpenTelemetry エクスポートに対応しており、ツール実行と権限判断を SIEM へ送れます。
AzureActivity
| where Caller == "<agent-service-principal-object-id>"
| project TimeGenerated, OperationNameValue, ActivityStatusValue, Caller, CallerIpAddress, ResourceGroup, _ResourceId, CorrelationId
| order by TimeGenerated descSIEM 連携は Microsoft Sentinel が第一候補で、Azure Activity や Entra ログのデータコネクタを備えます。「Azure の変更監査だけでなく、誰がどのプロンプトでどの az を打たせたか」まで相関できる設計が望ましく、エージェント側 OTel のツール実行イベント(タイムスタンプ+コマンド)を AzureActivity の行(タイムスタンプ+Caller+OperationName)に結合します。継続的な体制監視には Microsoft Defender for Cloud を使い、単発アラートより、推奨事項に owner と due date を持たせたガバナンスルールへ落とし込むと改善が回ります。
ログ基盤の保全も忘れないでください。攻撃者やエージェントが痕跡削除を狙う可能性があるため、監査ログの保存先は運用サブスクリプションから分離した不変ストレージに置きます。OWASP は access token・session ID・認証情報・暗号鍵をログへ残すなと明示しています。AI エージェントは会話ログ・観測ログ・トレースとログ面が厚いので、実行ログには request ID・command hash・ticket ID・Caller だけを残し、シークレットやトークンは残さない方針を徹底します。
日本・エンタープライズ固有の要件
日本のエンタープライズでは、ここに監査・準拠の文脈が乗ります。Azure は ISMAP(政府情報システムのためのセキュリティ評価制度)に登録済みで、東京・西日本を含むリージョンと多数のサービスが対象です。ただし ISMAP 登録は「言明対象範囲」単位なので、利用するサービスが範囲に含まれるかは ISMAP ポータルで確認が要ります。データレジデンシーは、後述の組織ガードレールでリージョンを日本に限定して担保します。
APPI(個人情報保護法)・ISMS の観点では、PII を含むリソースへのエージェントアクセスの可監査性、つまり「誰が・いつ・何にアクセスしたか」を追えることが要件になります。本記事で組み立てた Caller ベースの帰属、データプレーンの Resource Logs、Entra ログは、そのままこの要件への回答になります。
ベンチマーク面では、Microsoft クラウド セキュリティ ベンチマーク(MCSB)が AI セキュリティ領域を含む Azure 向けガイダンスを提供し、Key Vault セキュリティベースラインが ID 管理・特権アクセス・ログと脅威検出を具体化します。ただし Azure Policy の built-in initiative と CIS Microsoft Azure Foundations Benchmark の対応は「一対一ではなく、完全適合を保証しない」と Microsoft 自身が明記しています。CIS 側では 2026 年に Azure Foundations Benchmark の新版が公開されているため、「Azure Policy/Defender の準拠状態=CIS 準拠完了」とは扱わず、最新 CIS との差分管理表を別途持つのが現実的です。
なお、本記事はコンプライアンス助言ではありません。最終的な準拠判断は自組織の監査・法務と行ってください。
段階的に導入する
すべてを一度に入れる必要はありません。効果の大きい順に段階を切ります。
第 1 段階(即時・1 週間以内)
- エージェントを専用・最小権限の ID で認証する。Azure 内は Managed Identity、Azure 外は Workload Identity Federation を使い、クライアント シークレットは使わない。ユーザー資格情報は渡さない。
- エージェント ID を既定で読み取り系のカスタムロールに寄せ、非本番 resource group へスコープする。
Owner・User Access Administrator・サブスクリプションContributorは避ける。 - Claude Code・Codex などの
settings.jsonにaz * delete・az role assignment *・az ad sp *などの deny と、~/.azure・.envの Read deny を設定し、PreToolUsehook で破壊的・複合コマンドを確実にブロックする。 - 本番 resource group に
CanNotDeleteリソースロックを付け、エージェントにはlock deleteを禁止する。
第 2 段階(1 ヶ月以内)
- エージェントを Azure CLI 公式 Docker イメージの短命コンテナで動かし、
AZURE_CONFIG_DIRをジョブごとに分離し、終了時にaz logout/az account clear/az cache purgeで痕跡を消す。egress を Azure 管理/ログインエンドポイントに絞る。 managed-settings.json(disableBypassPermissionsMode: "disable"など)や Codex のrequirements.tomlで、開発者がポリシーを緩められないようにする。- シェルを介さないコマンドブローカーへ移行し、破壊系・IAM 変更・Key Vault 更新・Secret 参照に人手承認ゲートを挟む。
core.output=noneを既定にする。 - Key Vault の Resource Logs と Entra ログを有効化し、Activity Log とともに Log Analytics/Sentinel へ集約、削除・
role assignment変更・サインイン異常にアラートを張る。
第 3 段階(四半期)
- Key Vault を Azure RBAC・Private Endpoint・ソフト削除・消去保護付きで統制し、2026-02-01 以降の API へ移行する。
- 組織レベルで Azure Policy によるログ収集の強制とリージョン制限(日本限定)を適用し、Defender for Cloud の推奨事項に owner と due date を付ける。
- CI に prompt injection と tool abuse の悪用ケースの回帰テストを組み込み、危険な入力で承認フローや allowlist が破れないことを確認する。
判断を変える閾値も決めておきます。エージェントに書き込みや本番アクセスが恒常的に必要になったら、書き込みロールの付与前に第 2〜3 段階へ引き上げます。ネットワーク egress をサンドボックスできないなら、--dangerously-skip-permissions や danger-full-access は使いません。
限界と既知の制約
この設計も万能ではありません。前提を明示しておきます。
- エージェント側の permission/denylist は強制境界ではない: 複合コマンド・難読化・別プロセスによる敵対的回避に耐えない。唯一の信頼できる強制は RBAC・組織ポリシー・OS サンドボックス。
- トークンキャッシュの暗号化は OS 依存: MSAL キャッシュは Windows でのみ DPAPI 暗号化され、Linux/macOS では平文。これらの OS では分離・短命・削除が必須。
- PIM はエージェントのワークロード ID を覆わない: JIT 昇格はユーザー/グループ向けで、サービス プリンシパルは時間限定の active 割り当てのみ、Managed Identity は対象外。エージェント自身は狭い常時スコープで縛る。
- プロンプトインジェクションを単独で止める層はない: OWASP も完全な防止は存在しないとする。ここで挙げた制御は影響範囲と流出経路を縮めるものであって、リスクをゼロにはしない。
- 日付・バージョン依存の事実は再確認が要る: Key Vault の RBAC 既定(2026-02-01 API)、MFA Phase 2(2025 年 10 月)、Azure CLI 2.61 の secret warning、CIS の最新版は、自組織の現状で確認すること。
これらは欠陥というより、設計上のトレードオフです。要件が前提を超えたら、専用の非本番サブスクリプションやテナントへの隔離、microVM によるカーネル分離へ段階的に引き上げる合図だと捉えてください。
まとめ
AI エージェントにシェルと Azure の認証情報を同時に渡す。その便利さの裏側を、層で受け止める設計を組み立てました。要点を整理します。
- 認証はシークレットを捨て、Azure 内は Managed Identity、Azure 外は Workload Identity Federation で毎回短期トークンを取得する。ユーザー資格情報は渡さない。
- 唯一の強制境界である RBAC・組織ポリシーで最小権限を作り込み、カスタムロールと(人間側の)PIM で固める。エージェント自身のワークロード ID は狭い常時スコープで縛る。
- エージェント側の denylist・permission は利便性レイヤーと割り切り、LLM に自由実行させないコマンドブローカーとサンドボックス、
AZURE_CONFIG_DIR分離を本丸に据える。 - Azure 固有の
CanNotDeleteリソースロックで、エージェントの暴走から独立した削除バックストップを張る。 - Activity Log・Resource Logs・Entra ログ・エージェントログの監査四層と
Caller帰属、Sentinel/Defender for Cloud 検知で「誰が・いつ・何をしたか」を必ず追えるようにする。
一文でまとめるなら、「人間の ID から必要時だけ専用 ID を使わせ、エージェントには自由なシェルではなく構造化されたコマンドだけを、サンドボックスと承認フローとリソースロックの内側で渡し、Caller 単位の監査四層で必ず追跡可能にする」。入力ひとつの掌握では本番に届かない状態を作る。手数は増えますが、エージェントに Azure を預けるなら、これくらいの多層防御を既定にしておく価値があります。
以上、AI エージェント経由で Azure CLI を安全に使う設計を、現場からお送りしました。
参考情報
- Azure CLI とは
- Azure CLI でサインインする
- Azure リソースのマネージド ID の概要
- ワークロード ID フェデレーション
- Azure RBAC のベスト プラクティス
- Privileged Identity Management の構成
- リソースのロックによる変更や削除の防止
- Key Vault のベスト プラクティス
- Azure Monitor アクティビティ ログ
- Microsoft Sentinel の概要
- Microsoft Defender for Cloud とは
- OWASP: LLM01 Prompt Injection
- Microsoft Security: Defense in depth for autonomous AI agents
- Anthropic: How we built Claude Code auto mode
- Tom’s Hardware: Replit の AI エージェントが本番データベースを削除
- Claude Code: settings
- ISMAP ポータル