0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実機検証】AWS MCP Server を「セキュリティ視点」で解剖したら、条件キー1枚で最小権限・サンドボックスは想像以上に堅牢だった話

0
Posted at

免責事項
本記事は筆者個人の見解であり、所属組織の公式見解・開発体制・業務内容を示すものではありません。記事内の技術的表現は 2026年6月時点の実機検証に基づくものであり、特定の製品・サービスの性能・仕様を保証するものではありません。
なお、AWS アカウント ID・IP アドレス・リソース名などは検証環境の実値をマスク・置換しています。また一部の挙動(CloudTrail の sourceIPAddress / userAgent 等)は実機観測値であり、公式ドキュメントの記載とは異なる場合があります。実運用前にご自身の環境・最新の公式ドキュメントでの確認をお願いします。

はじめにmcp-hero-defense.png

こんにちは!Dirbatoの社内技術横断支援組織Backbeatに所属している柴田です!

2026年5月、AWS から AWS MCP Server(マネージドなリモート MCP)が GA しました。

AWS CLI 相当の操作を MCP ツール(call_aws / run_script 等)としてエージェントに公開する、AWS マネージドのリモート MCP サーバ。ローカルの mcp-proxy-for-aws が SigV4 署名してリモートエンドポイントに中継する。

AWS MCP Server のセキュリティについては、すでに優れた先行記事があります。Accenture(acntechjp)さんの記事条件キーによる人間とエージェントの権限分離を、フューチャーさんの記事IAM ガードレール設計と CloudTrail 監査を、それぞれ丁寧に検証されています(とても参考になりました。ありがとうございます)。

本記事は、それらと重複する条件キー(検証A)・CloudTrail 監査(検証B)は「再現と補強」にとどめ、まだ誰も実測していない次の3点に主眼を置きます。

  • :star: run_script の Python サンドボックスを内部まで解剖(検証C)— 何が遮断され、どこまで隔離されているか
  • :star: call_awsrun_script で権限境界は同じか(検証C+)— ツールごとにポリシーを書き分ける必要があるのか
  • :star: マネージド MCP とローカル MCP の使い分け(検証E)

「エージェントに AWS の鍵を渡して大丈夫なのか?」を、一段深い実装レベルで確かめます。検証は全5本(A〜E)構成です。

  1. 最小権限 — MCP 経由のリクエストだけを狙って Deny できるか(条件キー / 再現・補強
  2. 監査 — CloudTrail で「エージェント起点」を機械的に切り分けられるか(再現・補強
  3. :star: サンドボックスrun_script の Python 実行環境はどこまで隔離されているか
  4. :star: 権限の一貫性call_awsrun_script で権限境界は同じか
  5. :star: マルチアカウント / ローカル MCP との使い分け

環境: AWS アカウント 123456789012 / region ap-northeast-1 / IAM ユーザー amazonqmcp-proxy-for-aws==1.6.0


:dart: こんな人に読んでほしい

  • エージェント(Claude Code / Q 等)に AWS 操作を任せたいが、権限設計が不安な人
  • MCP 経由の操作を CloudTrail で監査・切り分けしたい人
  • run_script の Python サンドボックスがどこまで安全か知りたい人
  • マネージド AWS MCP Server と、ローカルの awslabs MCP 群の使い分けを決めたい人

:bulb: 要点サマリー

忙しい方向けに先に結論を。すべて実機で確認した一次データです。

検証項目 一言結果
:white_check_mark: 最小権限(条件キー) aws:CalledViaAWSMCP「どの MCP 経由か」を厳密に識別して Deny 可能。S3 バケットポリシー1枚で実証
:white_check_mark: 監査(CloudTrail) MCP 起点は sourceIPAddress / userAgent が共に aws-mcp.amazonaws.comuserIdentity は実ユーザーのまま → 切り分けと責任追跡が両立
:white_check_mark: サンドボックス(run_script) 実行前の静的 AST 検証os/socket/subprocess/eval 等を遮断。ファイルは /tmp 限定、外部通信は call_boto3(AWS API)経由のみ
:key: 権限の一貫性 同じ条件キー Deny が call_awsrun_script の両経路に効いた。「コードは隔離するが権限は隔離しない」
:warning: マルチアカウント 機能は実在するが プロキシ起動時のオプトインaws_profile パラメータ)。CLI の --profile は拒否される

:key: 最大の発見: AWS MCP Server は「便利な CLI ラッパー」ではなく、条件キー・CloudTrail・サンドボックスの三層でガバナンスを設計されたサービスだった。aws:CalledViaAWSMCPたった1つの Deny 文に入れるだけで、エージェントの全操作面(CLI 経路もサンドボックス内 boto3 経路も)を一律に制御できる。


1. アーキテクチャ — 鍵はローカルから出ない

  • 認証は既存の AWS 認証チェーン(~/.aws/credentials)で SigV4 署名。長期認証情報はローカルに留まる。
  • MCP Server 自体は無料。課金はエージェントが呼ぶ AWS API の実費のみ。
  • サーバ実体は us-east-1 / eu-central-1。叩く対象リージョンは --metadata AWS_REGION=... で指定。

ここで本記事の主役になるのが、MCP 経由のリクエストに AWS が自動で付与するグローバル条件キーです。

条件キー 意味
aws:ViaAWSMCPService Bool AWS マネージド MCP 経由なら true
aws:CalledViaAWSMCP String どの MCP サーバ経由か(例 aws-mcp.amazonaws.com / eks-mcp.amazonaws.com

📌 プレビュー期にあった IAM アクション aws-mcp:InvokeMcp / CallReadOnlyTool / CallReadWriteTool廃止済み・無効。現在は上記の条件キー方式が正典です。
出典: How AWS MCP Server works with IAM


2. 検証A:条件キーで「MCP 経由の削除」だけを Deny する

S3 バケットに、MCP 経由の DeleteObject だけを拒否するバケットポリシーを置きます。

{
  "Sid": "DenyDeleteViaAWSMCP",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:DeleteObject",
  "Resource": "arn:aws:s3:::mcp-poc-condkey-test-123456789012/*",
  "Condition": {
    "StringEquals": { "aws:CalledViaAWSMCP": "aws-mcp.amazonaws.com" }
  }
}

call_aws 経由で削除すると——

$ aws s3api delete-object --bucket mcp-poc-condkey-test-... --key obj-cplus-callaws.txt
An error occurred (AccessDenied) when calling the DeleteObject operation:
User: arn:aws:iam::123456789012:user/amazonq is not authorized to perform: s3:DeleteObject
... with an explicit deny in a resource-based policy

:white_check_mark: 狙い通り AccessDenied。条件値を eks-mcp.amazonaws.com(不一致)にすると同じ削除が成功することも確認しました。つまりこのキーは「MCP 経由かどうか」だけでなく 「どの MCP か」まで厳密に識別します。

💡 この条件キーは IAM ポリシー・SCP だけでなくリソースベースポリシー(バケットポリシー)でも使えます。「このバケットへの破壊的操作は、たとえ管理者でも MCP 経由なら拒否」を、リソース側1枚で完結できます。


3. 検証B:CloudTrail で「エージェント起点」を切り分ける

MCP 経由で実行された 下流の AWS API 呼び出し(例: PutBucketPolicy)を CloudTrail LookupEvents で見ると、実機ではこうなりました(今回の操作分を実測)。

// MCP 経由(aws-mcp で実行)
{
  "eventName":             "PutBucketPolicy",
  "eventSource":           "s3.amazonaws.com",
  "sourceIPAddress":       "aws-mcp.amazonaws.com",   // ← MCP 起点の刻印
  "userAgent":             "aws-mcp.amazonaws.com",   // ← 同上
  "eventType":             "AwsApiCall",
  "userIdentity.invokedBy":"aws-mcp.amazonaws.com",   // ← 「MCP 経由」をここでも識別可能
  "userIdentity.arn":      "arn:aws:iam::123456789012:user/amazonq"  // ← 実ユーザーは保持
}
// 比較: 同じ操作をマネジメントコンソールから実行
{
  "sourceIPAddress":  "203.0.113.x",                  // ← 実IP(マスク)
  "userAgent":        "Mozilla/5.0 ... <ブラウザ>",
  "userIdentity.arn": "arn:aws:iam::123456789012:root"
}

🔒 マスキングについて: 本記事の AWS アカウント ID(123456789012)、IP アドレス(203.0.113.x)、バケット名などは検証環境の実値をマスク・置換しています。挙動の本質は変わりません。

:white_check_mark: つまり、「誰が(実ユーザー)」と「何経由で(エージェント)」を同時に記録できる。下流 API の sourceIPAddress / userAgentaws-mcp.amazonaws.com になるので、これでフィルタすればエージェント起点の操作だけを機械抽出できます。AccessDenied で拒否された操作も記録されます。

📌 注記(事実の出どころ): 上記の sourceIPAddress / userAgent = aws-mcp.amazonaws.com本検証での実機観測値です(執筆時点で公式ドキュメントに明記された値ではありません)。文字列 aws-mcp.amazonaws.com は公式上は条件キー aws:CalledViaAWSMCP の値(サービスプリンシパル)として定義されています。なお同様の観測(userAgent / userIdentity.invokedBy = aws-mcp.amazonaws.comsourceIPAddress は固定値)はフューチャーさんの記事でも報告されており、本検証と整合します。userIdentity.invokedBy も併用すると、より堅牢に切り分けられます。

CloudTrail は2層で記録される

調べると、MCP 操作は CloudTrail 上で2つの層として残ることが分かりました。

何のイベントか 主なフィールド(公式 / 実測) 用途
下流 API 層 実際に叩かれた AWS API(PutBucketPolicy 等) eventSource=s3.amazonaws.com / sourceIP=userAgent=aws-mcp.amazonaws.com(実測) エージェントが資源に何をしたかを追跡
MCP メタ層 MCP ツール呼び出しそのもの eventSource=aws-mcp.<region>.api.aws / eventName=CallTool / eventType=AwsMcpEvent / requestParameters.method=call_aws(公式ログ例) どのツール(call_aws/run_script)を使ったかを追跡

下流 API 層で「資源への影響」を、MCP メタ層で「使われたツール」を見る——両方を突き合わせると監査が完成します。公式準拠でフィルタするなら、メタ層の eventType=AwsMcpEventrequestParameters.method が確実です。

出典(MCP メタ層のログ例): Logging AWS MCP Server with CloudTrail

⚠️ 注意: DeleteObject などのデータイベントは管理イベント履歴(LookupEvents)に出ません。オブジェクト単位で追うには S3 データイベントの記録を有効化する必要があります。


4. 検証C:run_script サンドボックスを解剖する

run_script は boto3 が書ける Python 実行環境です。「Python が動く」=「任意コードが動く」だと怖いので、どこまで隔離されているかを総当たりで実測しました。

4-1. 防御は「実行時」ではなく「コード検証時」

ブロック対象を含むスクリプトは、1行も実行されずに検証エラーで全件列挙・拒否されます。文字列リテラルのパスまで静的に見ています。

Code validation failed:
Line 2: Blocked import: os
Line 2: Blocked import: socket
Line 4: Blocked function: eval()
Line 3: Disallowed file path: x   ← open("x") はNG。ファイルは /tmp/ のみ

4-2. 遮断されているもの(実測)

種別 遮断対象(抜粋)
モジュール os, sys, socket, subprocess, urllib, http, ssl, ctypes, threading, multiprocessing, pickle, base64, hashlib, inspect, importlib, pathlib, shutil
ビルトイン関数 eval, exec, compile, __import__, getattr, setattr, globals, locals, vars, type, hasattr, id, memoryview
属性アクセス .__class__ 等の dunder 属性も静的拒否(型→基底クラス経由の脱出を封鎖)
ファイル open() は許可だが パスは /tmp/ 限定/etc/passwd 等は静的拒否

許可されるのは json / math / datetime / re / collections / asyncio などの純粋計算・データ整形系のみ

4-3. これが意味すること

攻撃面 結果
外部ネットワークへの持ち出し socket/urllib/ssl 不可 → 外部通信は call_boto3(AWS API)経由のみ
IMDS からの認証情報窃取 socket 不可 → 169.254.169.254 への接続コードすら書けない
ローカル秘密の読み出し os/sys 不可 → 環境変数に到達不能。FS は /tmp のみ
サンドボックス脱出 eval/exec/__import__/getattr/.__class__ を封鎖

:white_check_mark: 「Python が動く」とはいえ、外に出る手段と内省する手段を入口(AST 検証)で塞いだ強固なサンドボックスでした。なお run_script最低1つ call_boto3 を含むことも強制されます(AWS API を呼ばないスクリプトは "No AWS API call found" で拒否)。


5. 検証C+:権限境界は call_aws と run_script で同じか?

ここが本記事の山場です。run_script のサンドボックス内 call_boto3 も、実 IAM プリンシパル user/amazonq のまま実行されます(=コードは隔離するが権限は隔離しない)。

では、検証Aの「MCP 経由 DeleteObject Deny」は run_script 内の削除にも効くのか?

# run_script 内で削除を試行
await call_boto3(service_name="s3", operation_name="DeleteObject",
                 params={"Bucket": BUCKET, "Key": "obj-cplus-runscript.txt"})
# → AccessDenied: ... with an explicit deny in a resource-based policy

:key: 効きました。 call_awsrun_script(call_boto3)両方に同一の条件キー値 aws-mcp.amazonaws.com が刻印されます。

つまり、ガバナンス設計者にとっての結論はシンプルです——

aws:CalledViaAWSMCP を入れた Deny 文を1枚書けば、エージェントが CLI 経路で叩こうがサンドボックス内 boto3 で叩こうが、まとめて制御できる。

経路ごとにポリシーを書き分ける必要はありません。


6. 検証D:マルチアカウント / クロスロール

「別アカウント・別ロールに切り替えられるか」も試しました。直感的に CLI の --profile を付けると——

$ aws sts get-caller-identity --profile dev
The following global arguments cannot be set: --profile

:x: 拒否されます(cli_command 内のグローバル引数は禁止=正しい挙動)。正しい機構は別物でした。

項目 仕様(公式 + 実機)
正しい機構 プロキシが call_aws/run_script 等のツールスキーマに aws_profile パラメータを注入し、値でプロファイル別の専用接続にルーティング(バックエンド転送前に除去)
有効化条件 起動時に複数プロファイルを宣言した時だけ(--profile prod dev staging フラグ or AWS_MCP_PROXY_PROFILES 環境変数)
セキュリティ ①起動時アローリスト(エージェントは他プロファイルを発見不可)②ステートレスなパーコール署名 ③read-only を既定にし書込プロファイルは明示選択を推奨 ④prod 利用はクライアント側フックでゲート

:warning: 本セッションは単一プロファイル起動だったため aws_profile パラメータは出現せず、cross-account 経路は存在しませんでした。sts:AssumeRole 権限も持たないプリンシパルだったため、ライブのクロスロールは再現不可。**「設定していなければ、エージェントは勝手に別アカウントへ越境できない」**という事実自体がセキュリティ上の安心材料です。
出典: Multi-profile support


7. 検証E:マネージド AWS MCP Server と「ローカル MCP」の使い分け

awslabs はローカル実行型の MCP 群(aws-api-mcp-server、サービス別 MCP 等)も提供しています。マネージドな AWS MCP Server とどう棲み分けるか。

観点 マネージド AWS MCP Server ローカル MCP(awslabs 各種)
実行場所 リモート(aws-mcp.*.api.aws)。ローカルは SigV4 署名の中継のみ 自分のマシンで実行
認証情報の経路 ローカル資格情報で署名、API 実行はマネージド側 ローカル資格情報を直接使用
aws:CalledViaAWSMCP 刻印 あり(条件キーで一元ガバナンス可) なし(通常のユーザー API と区別不可)
CloudTrail 上の見え方 sourceIP/userAgent = aws-mcp.amazonaws.com 通常の API 呼び出しと同じ
サンドボックス run_script の AST 静的検証で強制 サーバ実装依存(隔離保証は基本なし)
カバー範囲 AWS CLI 相当を広く サービス特化・専用ツールが充実
オフライン / 閉域 不可(リモート前提) 可(ローカル完結)

:bulb: 使い分けの指針:

  • ガバナンス・監査を効かせて広く AWS を触らせたい → マネージド AWS MCP Server(条件キー+CloudTrail+サンドボックスの三層が効く)
  • 閉域・特定サービスの専用ツール・自前制御が要る → ローカル MCP

8. まとめ

検証 結論
A 最小権限 aws:CalledViaAWSMCP で「どの MCP 経由か」を厳密識別して Deny。リソースベースでも可
B 監査 sourceIP/userAgent = aws-mcp.amazonaws.com+実ユーザー保持で切り分けと追跡が両立
C サンドボックス AST 静的検証で os/socket/eval 等を遮断、FS は /tmp のみ、外部は call_boto3 だけ
C+ 権限の一貫性 同一 Deny が CLI 経路もサンドボックス boto3 経路も止める
D マルチアカウント aws_profile パラメータ(起動時オプトイン)。未設定なら越境不可
E 使い分け ガバナンス重視=マネージド/閉域・特化=ローカル

エージェントに AWS を任せる不安の多くは、**「条件キー1枚の Deny」と「CloudTrail の1フィルタ」**で実務的に解消できる、というのが実機検証の結論でした。AWS MCP Server は単なる便利ツールではなく、ガバナンスを前提に設計されたサービスです。

本記事は実機検証に基づくドラフトです。検証は単一アカウント・単一プロファイル・限定権限プリンシパルで実施しているため、マルチアカウント(検証D)はドキュメント仕様の確認に留まる点をご了承ください。


参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?