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】【AgentCore】AI エージェントが AWS リソースを参照できるようになるまで

0
Last updated at Posted at 2026-11-07

変更履歴

  • 2026-11-07:公開

初めに

Amazon Bedrock AgentCore は、AI エージェントを AWS で動かすための機能をまとめたサービスである。実行環境・会話の記憶・ツールの入口など、13 の機能がある(図1)。本記事では、紫の枠の Runtime・Gateway・Memory・Policy の 4 つを使う。

図1 Amazon Bedrock AgentCore の機能の全体像(2026-11-07 時点の公式ドキュメントの一覧より。紫の枠が本記事で使う機能)

AgentCore で動かしたエージェントに「EC2 の一覧を教えて」と聞く。この一言が実際の AWS の情報を返すまでには、いくつかの条件がある。答えの文を作る基盤モデルが賢ければ読めるわけではない。

エージェントが AWS を読むには、AWS の API を呼ぶコードがあり、そのコードを動かす主体に IAM の権限が要る。権限はどこに付けるのか。ツール(エージェントが呼べる関数)だけ足すと何が起きるのか。エージェントとツールの間に AgentCore Gateway という中継役を置くと、権限の置き場所はどう変わるのか。

この記事では、条件を 1 つずつ変えて確認した。あえて権限を外した構成も動かし、どこで止まるかを見ている。

結論

  • 今回の構成で AWS の API から新しく一覧を取るには、API を呼ぶコード(ツール)と、そのコードを動かす IAM ロールの権限の両方が要った。どちらか一方では取れなかった。
  • 中継役の AgentCore Gateway は必須ではなかった。Gateway と Lambda を挟むと、読み取りの権限の付け先が Lambda の IAM ロールへ移った。
  • ツールを外す・IAM で拒否する・Gateway の Policy(Gateway の入口でツール名と入力を見て許可・拒否する規則。IAM とは別の層)で拒否する、の 3 つは止まる場所が違った。Policy は Gateway を通らない呼び出しを止めなかった。

本記事の構成

1〜2 章で構成要素と判定のしかたを示し、3〜4 章で Runtime の中から直接呼ぶ場合、5〜6 章で Gateway 経由と止め方を見る。7 章は構成の選び方である。本筋からは外れるが、Memory から過去の一覧が返ったことを、最後の参考に書いた。

1. 登場する構成要素と、権限の主体

1.1 構成要素の役割

AgentCore でエージェントを動かすと、次の構成要素が関わる。役割はそれぞれ違う。

構成要素 役割
AgentCore Runtime エージェントのコードを動かす実行環境
基盤モデル(Amazon Bedrock) 質問を読み、答えの文を作り、呼ぶツールを選ぶ
ツール エージェントが呼べる関数。今回は AWS の API を呼ぶ処理を書く
AgentCore Gateway この記事では、ツールを MCP(AI エージェントとツールをつなぐ共通の決まり)の形でまとめて公開する入口として使う
AgentCore Policy Gateway の入口で、ツール名と入力を見て呼び出しを許可・拒否する仕組み
AgentCore Memory 会話から事実を抜き出して保存し、次の会話に読み出す仕組み。今回は長期の記憶(抜き出した事実)を扱う

モデルは「どのツールを呼ぶか」を選ぶだけで、AWS の認証情報は持っていない。AWS の API を呼ぶのはツールのコードである。

1.2 権限の主体

同じ「エージェントの権限」でも、IAM のどのロールの話かは経路で変わる。今回出てくる主体(IAM のプリンシパル)は 4 つある。

主体 何の権限か
呼び出す人(今回は専用の IAM ロール permlab-caller) Runtime を呼ぶ
Runtime の実行ロール Runtime の中のコードが AWS を呼ぶ
Gateway サービスロール Gateway が Lambda を呼ぶ
Lambda の実行ロール Lambda の中のコードが AWS を呼ぶ

1.3 比べる 2 つの経路

ツールの置き場所は 2 通りある。

  • 直接のツール:Runtime の中の Python の関数が Boto3 で AWS を呼ぶ。AWS を読む権限は Runtime の実行ロールに要る。
  • Gateway 経由のツール:Runtime が Gateway を呼び、Gateway が Lambda を呼び、Lambda が Boto3 で AWS を呼ぶ。Runtime の実行ロールに InvokeGateway、Gateway サービスロールに lambda:InvokeFunction、Lambda の実行ロールに AWS を読む権限が要る。

図2 直接のツールと Gateway 経由のツールの経路と、権限の主体(赤い字が矢印に要る権限とその持ち主)

今回は Gateway を、ツールの入口としてだけ使う。

2. 検証の環境と、成否の判定のしかた

2.1 環境

項目 値
リージョン 東京(ap-northeast-1)
AgentCore CLI 1.0.2(プロジェクトを作ってデプロイするコマンド)
テンプレート agent-python-strands(Python のエージェント用の枠組み Strands Agents を使う雛形)
モデル jp.amazon.nova-2-lite-v1:0
Python(Runtime) 3.14.2
Strands Agents 1.64.0
Boto3 1.43.126
mcp-proxy-for-aws 1.8.0(Gateway に SigV4 で署名してつなぐ MCP のクライアント)
実施日 2026-11-07

読み取りの対象として、EC2 を 2 台(起動中と停止中)と、RDS(AWS のマネージドのデータベース)の DB インスタンスを 1 台(PostgreSQL)作った。S3 バケットは 2 個で、1 個は読み取り用、もう 1 個は 6 章の書き込みの実験用である。どれも検証用で、中に業務のデータは無い。
作った資源は、コンソールでは次のように見える。

図3 検証用の EC2(起動中と停止中の 2 台)

図4 検証用の RDS

図5 S3 バケットの一覧(CDK のバケットを含む 3 個)

S3 は検証用の 2 個のほかに、デプロイのときに CLI が使う CDK(AWS のリソースをコードで作る道具)のバケットも一覧に出る。

正解は、管理者の AWS CLI で全件を取って決めた(ID は伏せた)。

質問 正解
EC2 permlab-running(実行中・t4g.nano)と permlab-stopped(停止済み・t4g.nano)の 2 台
RDS permlab-db(postgres・利用可能)の 1 台
S3 検証用の 2 個と CDK の 1 個の 3 個

2.2 エージェントに聞く質問

以降の比較では、次の同じ文を聞く。リージョンを変える、書き込みを頼むなどの追加の試験は、その場で文を示す。

ID 質問
Q1 東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。
Q2 東京リージョンの RDS DB インスタンスの識別子・エンジン・状態を教えて。取得できない場合は推測しないで。
Q3 この AWS アカウントの S3 バケット名の一覧を取得して。取得できない場合は推測しないで。

Q1 と Q2 はリージョンの中の一覧、Q3 はアカウント全体の一覧である。範囲が違うので、件数は比べない。

2.3 判定のしかた

エージェントの返事だけでは、実際に AWS を読んだかは分からない。それらしい一覧を作文しただけかもしれないからである。
そこで、次の 3 つを突き合わせて判定した。

見るもの 分かること
エージェントの応答とログ どのツールを呼んだか、何が返ったか
CloudTrail(AWS の API の呼び出しを記録するサービス)のイベント履歴 どのロールで読み取りの API が呼ばれたか、拒否されたか
管理者の AWS CLI で取った全件 正解

S3 の書き込みはイベント履歴に出ないので、API の応答と、管理者の AWS CLI で見たオブジェクトの有無で判定した。

1 回の実行ごとに、モデルがツールを呼んだか、API が通ったか、最後の返事が正しいかを分けて記録した。

同じ質問でも、モデルがツールを呼ぶかどうかは毎回同じとは限らない。そこで主な比較は、同じ質問を 3 回ずつ聞いた。聞くたびに新しいセッション(Runtime の会話の単位)を使い、前の会話を引き継がないようにした。何回聞いたかは、各章の結果の前に書いた。

ただし、セッションを変えても前の結果が残ることがあった。Memory のためで、記事の終わりの参考に書く。

これとは別に、モデルの揺れと権限の結果を分けて見るため、検証用の命令を足した。/probe ec2 と送ると、モデルを呼ばずに Runtime の中から DescribeInstances を呼び、結果をそのまま返す。本番の Runtime には残さない。

3. テンプレートのままで何ができるか

agent-python-strands のテンプレートをそのままデプロイした。

agentcore create --name AgentcorePermissionsLab --template agent-python-strands
cd AgentcorePermissionsLab
agentcore deploy
  • agentcore create:テンプレート agent-python-strands から、プロジェクトのフォルダを作る。エージェントのコード(app/agent/)と設定(agentcore/agentcore.json)が入る。
  • cd:作ったフォルダに入る。
  • agentcore deploy:設定のとおりに AWS に作る。CDK で Runtime・Memory・Runtime の実行ロールができる。

3.1 作られたもの

作られたのは Runtime 1 つと Memory 1 つで、Gateway は無い。
Runtime に登録されたツールは、2 つの数を足す add_numbers だけだった。

図6 デプロイしたあとの Runtime の詳細(ステータスはアクティブ)

main.py は、agentcore create が作る app/agent/main.py で、Runtime で動くエージェントの本体である。中身は次のとおりである。

  • @tool を付けた関数が、モデルに渡すツールになる。
  • tools の一覧に入れた関数だけが、エージェントに渡る。
  • _get_agent が、モデル・Memory(get_memory_session_manager)・ツールをまとめてエージェントを作る。actor_id は、呼び出しの入力の actorId から取る。
テンプレートが作った main.py の全文
main.py
from functools import lru_cache

from strands import Agent, tool
from strands.agent.conversation_manager.null_conversation_manager import NullConversationManager
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from model.load import load_model
from parse import parse_payload
from memory.session import get_memory_session_manager

DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant. Use tools when appropriate."

# Define a collection of tools used by the model
tools = []

# Define a simple function tool
@tool
def add_numbers(a: int, b: int) -> int:
    """Return the sum of two numbers"""
    return a + b

tools.append(add_numbers)

def _make_conversation_manager():
    return NullConversationManager()

@lru_cache(maxsize=128)
def _get_agent(session_id: str, actor_id: str) -> Agent:
    """Given a session_id and actor_id, return or construct the corresponding Strands agent.
    Note: caching helps avoid repeated identity fetches on non-bedrock model loads
    and supports in-memory session management for local dev."""
    return Agent(
        model=load_model(),
        session_manager=get_memory_session_manager(session_id, actor_id),
        conversation_manager=_make_conversation_manager(),
        system_prompt=DEFAULT_SYSTEM_PROMPT,
        tools=tools,
    )

def create_app():
    app = BedrockAgentCoreApp()
    log = app.logger

    @app.entrypoint
    async def invoke(payload, context):
        log.info("Invoking Agent.....")

        session_id = getattr(context, "session_id", None) or "default-session"
        prompt, actor_id = parse_payload(payload)

        log.info(f"Invoking with session_id={session_id} and actor_id={actor_id}")
        agent = _get_agent(session_id, actor_id)

        async for event in agent.stream_async(prompt):
            if not isinstance(event, dict) or "event" not in event:
                continue
            cbs = event["event"].get("contentBlockStart")
            if cbs is not None and not cbs.get("start"):
                continue
            yield event

    return app

app = create_app()
if __name__ == "__main__":
    app.run()

テンプレートの既定のモデルは global.anthropic.claude-sonnet-5-5 だった。検証のアカウントでは AccessDeniedException になり、最初の質問から答えが返らなかった。使えるモデルはアカウントで違う。今回は app/agent/model/load.py を jp.amazon.nova-2-lite-v1:0 に変えて進めた。

app/agent/model/load.py
 from strands.models.bedrock import BedrockModel

 def load_model() -> BedrockModel:
     """Get Bedrock model client using IAM credentials."""
-    return BedrockModel(model_id="global.anthropic.claude-sonnet-5-5")
+    return BedrockModel(model_id="jp.amazon.nova-2-lite-v1:0")

3.2 Runtime の実行ロールの権限

Runtime のコードが AWS を呼ぶときは、Runtime の実行ロールの権限を使う。ロールは自分で用意していない。CLI は、agentcore.json で executionRoleArn を指定しなければ、デプロイのときに CDK で Runtime の実行ロールを作る。今回はこの既定のロールを使った。

作られたロールには、インラインポリシーが 1 つだけ付いていた。管理ポリシーは付いていない。中身を用途で分けると次のとおりで、EC2 と RDS の権限は無い。

用途 主なアクション 対象
ログ logs:CreateLogStream・logs:PutLogEvents など Runtime のロググループ
トレースとメトリクス xray:PutTraceSegments など・cloudwatch:PutMetricData すべて(メトリクスは名前空間 bedrock-agentcore だけ)
モデルの呼び出し bedrock:InvokeModel・bedrock:InvokeModelWithResponseStream・bedrock:CountTokens すべての推論プロファイルと基盤モデル
Memory CreateEvent・ListEvents・RetrieveMemoryRecords など このプロジェクトの Memory だけ。記録の読み出しは /users/*/facts と /episodes/*/* に限る
エージェントの ID GetWorkloadAccessToken など 既定のワークロード ID(AgentCore Identity)
設定のバンドル GetConfigurationBundle など エージェントの設定を版で管理するもの
S3 s3:GetObject*・s3:List* など(読み取りだけ) CDK のアセット(デプロイに使うファイル)のバケットだけ
インラインポリシーの全文(アカウント ID と Memory の ID は伏せた)
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:DescribeLogStreams"
            ],
            "Resource": "arn:aws:logs:ap-northeast-1:<アカウント ID>:log-group:/aws/bedrock-agentcore/runtimes/*",
            "Effect": "Allow",
            "Sid": "LogGroupAccess"
        },
        {
            "Action": "logs:DescribeLogGroups",
            "Resource": "arn:aws:logs:ap-northeast-1:<アカウント ID>:log-group:*",
            "Effect": "Allow",
            "Sid": "DescribeLogGroups"
        },
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:ap-northeast-1:<アカウント ID>:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*",
            "Effect": "Allow",
            "Sid": "LogStreamAccess"
        },
        {
            "Action": [
                "xray:GetSamplingRules",
                "xray:GetSamplingTargets",
                "xray:PutTelemetryRecords",
                "xray:PutTraceSegments"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Sid": "XRayAccess"
        },
        {
            "Condition": {
                "StringEquals": {
                    "cloudwatch:namespace": "bedrock-agentcore"
                }
            },
            "Action": "cloudwatch:PutMetricData",
            "Resource": "*",
            "Effect": "Allow",
            "Sid": "CloudWatchMetrics"
        },
        {
            "Action": [
                "bedrock-agentcore:GetWorkloadAccessToken",
                "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
                "bedrock-agentcore:GetWorkloadAccessTokenForUserId"
            ],
            "Resource": [
                "arn:aws:bedrock-agentcore:ap-northeast-1:<アカウント ID>:workload-identity-directory/default",
                "arn:aws:bedrock-agentcore:ap-northeast-1:<アカウント ID>:workload-identity-directory/default/workload-identity/*"
            ],
            "Effect": "Allow",
            "Sid": "GetAgentAccessToken"
        },
        {
            "Action": [
                "bedrock:CountTokens",
                "bedrock:InvokeModel",
                "bedrock:InvokeModelWithResponseStream"
            ],
            "Resource": [
                "arn:aws:bedrock:*:<アカウント ID>:inference-profile/*",
                "arn:aws:bedrock:*::foundation-model/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "logs:FilterLogEvents",
                "logs:GetLogEvents",
                "logs:PutResourcePolicy"
            ],
            "Resource": "arn:aws:logs:ap-northeast-1:<アカウント ID>:log-group:/aws/bedrock-agentcore/runtimes/*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "bedrock-agentcore:CreateConfigurationBundle",
                "bedrock-agentcore:DeleteConfigurationBundle",
                "bedrock-agentcore:GetConfigurationBundle",
                "bedrock-agentcore:GetConfigurationBundleVersion",
                "bedrock-agentcore:ListConfigurationBundleVersions",
                "bedrock-agentcore:ListConfigurationBundles",
                "bedrock-agentcore:UpdateConfigurationBundle"
            ],
            "Resource": "arn:aws:bedrock-agentcore:*:*:configuration-bundle/*",
            "Effect": "Allow"
        },
        {
            "Condition": {
                "StringLike": {
                    "bedrock-agentcore:namespace": [
                        "/users/*/facts",
                        "/episodes/*/*"
                    ]
                }
            },
            "Action": [
                "bedrock-agentcore:ListMemoryRecords",
                "bedrock-agentcore:RetrieveMemoryRecords"
            ],
            "Resource": "arn:aws:bedrock-agentcore:ap-northeast-1:<アカウント ID>:memory/AgentcorePermissionsLab_default_agentMemory-<ID>",
            "Effect": "Allow"
        },
        {
            "Condition": {
                "StringLike": {
                    "bedrock-agentcore:namespacePath": [
                        "/users/*/facts",
                        "/episodes/*/*"
                    ]
                }
            },
            "Action": [
                "bedrock-agentcore:ListMemoryRecords",
                "bedrock-agentcore:RetrieveMemoryRecords"
            ],
            "Resource": "arn:aws:bedrock-agentcore:ap-northeast-1:<アカウント ID>:memory/AgentcorePermissionsLab_default_agentMemory-<ID>",
            "Effect": "Allow"
        },
        {
            "Action": [
                "bedrock-agentcore:CreateEvent",
                "bedrock-agentcore:DeleteEvent",
                "bedrock-agentcore:DeleteMemoryRecord",
                "bedrock-agentcore:GetEvent",
                "bedrock-agentcore:GetMemory",
                "bedrock-agentcore:GetMemoryRecord",
                "bedrock-agentcore:ListActors",
                "bedrock-agentcore:ListEvents",
                "bedrock-agentcore:ListSessions"
            ],
            "Resource": "arn:aws:bedrock-agentcore:ap-northeast-1:<アカウント ID>:memory/AgentcorePermissionsLab_default_agentMemory-<ID>",
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:GetBucket*",
                "s3:GetObject*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::cdk-hnb659fds-assets-<アカウント ID>-ap-northeast-1",
                "arn:aws:s3:::cdk-hnb659fds-assets-<アカウント ID>-ap-northeast-1/*"
            ],
            "Effect": "Allow"
        }
    ]
}

信頼ポリシーでは、このアカウントの、このプロジェクトの Runtime だけが、bedrock-agentcore.amazonaws.com を通してこのロールを引き受けられる。

信頼ポリシーの全文
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "bedrock-agentcore.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "<アカウント ID>"
                },
                "ArnLike": {
                    "aws:SourceArn": "arn:aws:bedrock-agentcore:ap-northeast-1:<アカウント ID>:runtime/AgentcorePermissionsLab_default_agent*"
                }
            }
        }
    ]
}

図7 Runtime の実行ロールの許可(2 行目の permlab-ec2-read は 4 章で足したもの。撮ったのは 4 章のあと)

3.3 テンプレートのままで聞いた結果

Q1〜Q3 を 3 回ずつ聞いた。9 回ともツールは呼ばれず、「使えるツールが無い」と返った。実データのように作文した回は無かった。ただし Q3 の 1 回目は、最後に「EC2 の情報なら取得できるツールがある」と、無いツールがあるかのように書き添えた。

9 回の応答の全文

Q1:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。

1 回目(呼んだツール:なし)

申し訳ありませんが、現在提供されているツールでは、東京リージョンにある EC2 インスタンスの ID・状態・インスタンスタイプを一覧にする機能はありません。提供されているツールは「2つの数値の合計を計算する」というもののみです。EC2 インスタンスの情報は、AWS の API や AWS CLI などの適切なツールを使用することで取得可能です。

もし、2つの数値の合計を計算する必要があれば、お知らせください。喜んでお手伝いします。

2 回目(呼んだツール:なし)

申し訳ありませんが、私は提供されたツールには「東京リージョンのEC2インスタンスの情報を取得する機能」が含まれていません。提供されたツールは2つの数値の合計を計算するもののみです。EC2インスタンスの情報は、AWSの公式APIまたはAWS CLIを使用して取得する必要があります。

現在、私は次の関数しか使用できません:
- `add_numbers`: 2つの数値の合計を計算します

EC2インスタンスの情報を取得するには、以下の方法が考えられます:
1. AWS Management ConsoleでEC2ダッシュボードにアクセス
2. AWS CLIコマンド `aws ec2 describe-instances --region ap-northeast-1` を使用
3. AWS SDK(Pythonのboto3など)を使用

何か他の計算が必要な場合は、お手伝いできます。

3 回目(呼んだツール:なし)

申し訳ありませんが、現在提供されているツールには、EC2インスタンスの情報を取得する機能はありません。提供されている唯一のツールは 2 つの数値の合計を計算するものです。EC2インスタンスの情報は、AWS管理コンソール、AWS CLI、またはAWS SDKを使用して取得する必要があります。

Q2:東京リージョンの RDS DB インスタンスの識別子・エンジン・状態を教えて。取得できない場合は推測しないで。

1 回目(呼んだツール:なし)

申し訳ありませんが、現在私は東京リージョンに関するRDS DBインスタンス情報を取得するための機能を持っていません。提供されたツールは数値の加算のみに対応しており、AWSリソースのクエリには対応していません。

RDS DBインスタンスの詳細を調べるには、以下のいずれかの方法が考えられます:

1. AWS Management ConsoleでRDSサービスにアクセスし、東京リージョンのDBインスタンスを確認
2. AWS CLIを使用して次のようにコマンドを実行:
   ```bash
   aws rds describe-db-instances --region ap-northeast-1
   ```
3. AWS SDK(Pythonのboto3など)を使用してプログラム的に取得

これらの方法で、識別子、エンジン、状態などの情報を確認することができます。

2 回目(呼んだツール:なし)

申し訳ありませんが、現在私は東京リージョン(ap-northeast-1)の RDS DB インスタンスに関する情報にアクセスする機能は持っていません。提供されたツールには RDS の情報を取得するための関数が含まれていないため、RDS DB インスタンスの識別子・エンジン・状態を正確に提供することができません。

RDS の情報が必要な場合は、以下のようにしてください:

1. **AWS Management Console** を利用して直接確認
2. **AWS CLI** で `aws rds describe-db-instances --region ap-northeast-1` を実行
3. **AWS SDK**(Pythonのboto3など)を使用してプログラムで取得

何か他のご質問があればお手伝いします。

3 回目(呼んだツール:なし)

申し訳ありませんが、現在ご提供されているツールには Amazon RDS の情報を取得するための機能は含まれていません。EC2 インスタンスの情報も同様に、提供された `add_numbers` 関数では対応できません。

RDS DB インスタンスの詳細(識別子、エンジン、状態)を取得するには、AWS CLI、AWS SDK、または AWS Management Console などのツールを使用する必要があります。以下のいずれかの方法で情報を取得できます:

### AWS CLI を使用する場合
```bash
aws rds describe-db-instances --region ap-northeast-1
```

このコマンドは、東京リージョン(ap-northeast-1)にあるすべての RDS DB インスタンスの詳細情報を返します。出力には、識別子(DBInstanceIdentifier)、エンジン(Engine)、状態(DBInstanceStatus)が含まれています。

### AWS Management Console を使用する場合
1. AWS Management Console にログインします。
2. **RDS** に移動します。
3. **データベース** タブを選択します。
4. リストからインスタンスを選択すると、詳細情報が表示されます。

お手持ちの AWS アカウントにアクセス権がある場合は、これらの方法で情報を取得してください。何か他のサポートが必要な場合はお知らせください。

Q3:この AWS アカウントの S3 バケット名の一覧を取得して。取得できない場合は推測しないで。

1 回目(呼んだツール:なし)

申し訳ありませんが、現在ご提供いただけるツールには S3 バケット名の一覧を取得する機能は含まれていません。提供されているツールは2つの数値の合計を計算する機能のみです。

S3 バケットの一覧を取得するには、AWS CLI や AWS SDK など、適切な AWS 連携ツールを使用する必要があります。例えば AWS CLI を使用する場合は次のコマンドが有効です:

```bash
aws s3 ls --query "Buckets[].Name" --output text
```

これにより、すべての S3 バケット名がリスト形式で表示されます。

ご質問が EC2 インスタンスの情報に関連している場合は、その情報も取得可能なツールがありますので、お知らせください。

2 回目(呼んだツール:なし)

申し訳ありませんが、提供されたツールでは S3 バケット名の一覧を取得する機能がありません。現在利用可能なツールは2つの数値の合計を計算するもののみです。S3 バケットの一覧を取得するには、AWS の `list-buckets` コマンドや AWS SDK、AWS マネジメントコンソールなどを使用する必要があります。

現在ご提供できるのは、2つの数値の合計を計算することのみです。たとえば、2つの数値を入力して合計を計算することができます。何か他に計算が必要な場合はお知らせください。

3 回目(呼んだツール:なし)

申し訳ございませんが、現在ご提供されている機能では S3 バケット名の一覧を取得する機能はありません。提供されている関数は2つの数値の合計を計算するためのもので、S3 バケットへのアクセスはできません。

S3 バケットの一覧を取得するには、以下のいずれかの方法が考えられます:

1. **AWS CLI を使用する方法**:
```bash
aws s3 ls
```

2. **AWS Management Console を使用する方法**:
- AWS コンソールにログイン
- S3 サービスに移動
- バケットの一覧が表示されます

3. **AWS SDK を使用する方法**(Python の例):
```python
import boto3

s3 = boto3.client('s3')
response = s3.list_buckets()

for bucket in response['Buckets']:
    print(bucket['Name'])
```

これらの方法のいずれかを使用して S3 バケット名の一覧を取得してください。

4. IAM とツールを別々に足す

4.1 足したコードと IAM の許可

3.1 のテンプレートの main.py に、Boto3 で EC2 を読むツール list_ec2_instances を足した。

main.py
@tool
def list_ec2_instances(region: str = DEFAULT_REGION) -> dict:
    """List EC2 instances in a region. Returns ID, state, type and Name tag."""
    return logged("list_ec2_instances", {"region": region}, read_ec2(region))

read_ec2 が Boto3 の describe_instances を呼び、logged が結果をログに 1 行残す。

4.2 では、このツールをモデルに渡す場合と渡さない場合を比べる。そのたびにコードを書き換えなくて済むように、渡すツールを環境変数 LAB_TOOLS で選べるようにした。LAB_TOOLS に ec2 と書けば EC2 のツールを渡し、空ならテンプレートの add_numbers だけを渡す。環境変数は agentcore.json の Runtime の envVars に書き、デプロイし直して切り替えた。

main.py
def build_tools():
    tools = [add_numbers]
    if "ec2" in LAB_TOOLS:
        tools += [list_ec2_instances, describe_ec2_instance]
    if "rds" in LAB_TOOLS:
        tools.append(list_rds_instances)
    if "s3" in LAB_TOOLS:
        tools.append(list_s3_buckets)
    if "marker" in LAB_TOOLS:
        tools.append(put_test_marker)
    if LAB_GATEWAY_URL and os.environ.get("LAB_GATEWAY_TOOLS", "on") == "on":
        tools.append(gateway_client())
    return tools

build_tools() の戻り値を、テンプレートの tools の代わりにエージェントへ渡す。書き込みのツールと Gateway のツールも、同じ関数で足している。

検証で使った main.py の全文(ツール・検証用の命令・ログを含む)
main.py
# Qiita_AgentCore権限 の検証用:テンプレートの main.py に、次の 3 つを足したもの
#
#   1. AWS を呼ぶツール(環境変数 LAB_TOOLS で登録を切り替える)
#   2. モデルを通さない確認の命令(/whoami・/tools・/probe・/put・/gw-list・/gw-call)。LAB_PROBE=on のときだけ動く
#   3. ツールの実行を 1 行の JSON で標準出力に出す lab_log
#
# 置き場:agentcore create で作った app/agent/main.py を、このファイルで置き換える。
# LAB_TOOLS・LAB_PROBE・LAB_MARKER_BUCKET・LAB_READ_BUCKET・LAB_GATEWAY_URL は agentcore.json の envVars で渡す。
# 手順は [[Qiita_AgentCore権限_手順書]]。
#
# 出典(2026-11-07 に確認):
#   https://docs.aws.amazon.com/bedrock/latest/userguide/kb-gateway-target.html(aws_iam_streamablehttp_client の例)

import asyncio
import json
import os
import shlex
from functools import lru_cache

import boto3
from botocore.exceptions import ClientError
from strands import Agent, tool
from strands.agent.conversation_manager.null_conversation_manager import NullConversationManager
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from model.load import load_model
from parse import parse_payload
from memory.session import get_memory_session_manager

DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant. Use tools when appropriate."
ALLOWED_REGIONS = {"ap-northeast-1", "us-east-1"}
DEFAULT_REGION = "ap-northeast-1"
MARKER_KEY = "markers/marker.txt"

LAB_TOOLS = {t.strip() for t in os.environ.get("LAB_TOOLS", "").split(",") if t.strip()}
LAB_GATEWAY_URL = os.environ.get("LAB_GATEWAY_URL") or os.environ.get("AGENTCORE_GATEWAY_RESOURCEGATEWAY_URL", "")
LAB_MARKER_BUCKET = os.environ.get("LAB_MARKER_BUCKET", "")
LAB_READ_BUCKET = os.environ.get("LAB_READ_BUCKET", "")
LAB_PROBE = os.environ.get("LAB_PROBE") == "on"

def aws(service, region=None):
    # 読み込みのときに作ったクライアントを持ち越さないよう、呼ばれるたびに作る
    return boto3.Session().client(service, region_name=region or DEFAULT_REGION)

def lab_log(**fields):
    print("LAB " + json.dumps(fields, ensure_ascii=False, default=str), flush=True)

def aws_error(err):
    meta = err.response.get("ResponseMetadata", {})
    return {
        "ok": False,
        "error_code": err.response.get("Error", {}).get("Code"),
        "message": err.response.get("Error", {}).get("Message"),
        "request_id": meta.get("RequestId"),
    }

def check_region(region):
    if region not in ALLOWED_REGIONS:
        return {"ok": False, "error_code": "RegionNotAllowed", "message": region}
    return None

def read_ec2(region, instance_id=None):
    bad = check_region(region)
    if bad:
        return bad
    args = {"InstanceIds": [instance_id]} if instance_id else {}
    try:
        res = aws("ec2", region).describe_instances(**args)
    except ClientError as err:
        return aws_error(err)
    items = []
    for reservation in res["Reservations"]:
        for inst in reservation["Instances"]:
            tags = {t["Key"]: t["Value"] for t in inst.get("Tags", [])}
            items.append({
                "id": inst["InstanceId"],
                "state": inst["State"]["Name"],
                "type": inst["InstanceType"],
                "name": tags.get("Name"),
            })
    return {"ok": True, "count": len(items), "items": items,
            "request_id": res["ResponseMetadata"]["RequestId"]}

def read_rds(region):
    bad = check_region(region)
    if bad:
        return bad
    try:
        res = aws("rds", region).describe_db_instances()
    except ClientError as err:
        return aws_error(err)
    items = [{"id": d["DBInstanceIdentifier"], "engine": d["Engine"], "status": d["DBInstanceStatus"]}
             for d in res["DBInstances"]]
    return {"ok": True, "count": len(items), "items": items,
            "request_id": res["ResponseMetadata"]["RequestId"]}

def read_s3():
    try:
        res = aws("s3").list_buckets()
    except ClientError as err:
        return aws_error(err)
    items = [b["Name"] for b in res["Buckets"]]
    return {"ok": True, "count": len(items), "items": items,
            "request_id": res["ResponseMetadata"]["RequestId"]}

def write_object(bucket, key):
    try:
        res = aws("s3").put_object(Bucket=bucket, Key=key, Body=b"permlab\n")
    except ClientError as err:
        return aws_error(err)
    return {"ok": True, "key": key, "request_id": res["ResponseMetadata"]["RequestId"]}

def write_marker():
    return write_object(LAB_MARKER_BUCKET, MARKER_KEY)

def logged(name, inputs, result):
    lab_log(kind="tool", tool=name, input=inputs, ok=result.get("ok"), count=result.get("count"),
            error_code=result.get("error_code"), request_id=result.get("request_id"))
    return result

@tool
def add_numbers(a: int, b: int) -> int:
    """Return the sum of two numbers"""
    return a + b

@tool
def list_ec2_instances(region: str = DEFAULT_REGION) -> dict:
    """List EC2 instances in a region. Returns ID, state, type and Name tag."""
    return logged("list_ec2_instances", {"region": region}, read_ec2(region))

@tool
def describe_ec2_instance(instance_id: str, region: str = DEFAULT_REGION) -> dict:
    """Return the state of one EC2 instance by its instance ID."""
    return logged("describe_ec2_instance", {"instance_id": instance_id, "region": region},
                  read_ec2(region, instance_id))

@tool
def list_rds_instances(region: str = DEFAULT_REGION) -> dict:
    """List RDS DB instances in a region. Returns identifier, engine and status."""
    return logged("list_rds_instances", {"region": region}, read_rds(region))

@tool
def list_s3_buckets() -> dict:
    """List the S3 bucket names in this AWS account."""
    return logged("list_s3_buckets", {}, read_s3())

@tool
def put_test_marker() -> dict:
    """Create marker.txt under the fixed prefix of the dedicated test S3 bucket."""
    return logged("put_test_marker", {}, write_marker())

def gateway_client():
    from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
    from strands.tools.mcp.mcp_client import MCPClient

    return MCPClient(lambda: aws_iam_streamablehttp_client(
        endpoint=LAB_GATEWAY_URL,
        aws_region=DEFAULT_REGION,
        aws_service="bedrock-agentcore",
    ))

def build_tools():
    tools = [add_numbers]
    if "ec2" in LAB_TOOLS:
        tools += [list_ec2_instances, describe_ec2_instance]
    if "rds" in LAB_TOOLS:
        tools.append(list_rds_instances)
    if "s3" in LAB_TOOLS:
        tools.append(list_s3_buckets)
    if "marker" in LAB_TOOLS:
        tools.append(put_test_marker)
    if LAB_GATEWAY_URL and os.environ.get("LAB_GATEWAY_TOOLS", "on") == "on":
        tools.append(gateway_client())
    return tools

def _make_conversation_manager():
    return NullConversationManager()

@lru_cache(maxsize=128)
def _get_agent(session_id: str, actor_id: str) -> Agent:
    return Agent(
        model=load_model(),
        session_manager=get_memory_session_manager(session_id, actor_id),
        conversation_manager=_make_conversation_manager(),
        system_prompt=DEFAULT_SYSTEM_PROMPT,
        tools=build_tools(),
    )

def error_chain(err, depth=0):
    # MCP のクライアントは原因を例外グループの中に包むので、中身まで開く
    found = []
    children = list(getattr(err, "exceptions", []) or [])
    if err.__cause__:
        children.append(err.__cause__)
    for child in children:
        found.append(f"{type(child).__name__}: {str(child)[:300]}")
        if depth < 4:
            found += error_chain(child, depth + 1)
    return found

def run_command(text):
    parts = shlex.split(text)
    name, args = parts[0], parts[1:]

    if not LAB_PROBE:
        return {"ok": False, "error_code": "ProbeDisabled"}

    if name == "/whoami":
        try:
            res = aws("sts").get_caller_identity()
        except ClientError as err:
            return aws_error(err)
        return {"ok": True, "arn": res["Arn"]}

    if name == "/versions":
        from importlib.metadata import version
        import platform
        names = ["boto3", "botocore", "strands-agents", "bedrock-agentcore", "mcp-proxy-for-aws", "mcp"]
        return {"ok": True, "python": platform.python_version(), **{n: version(n) for n in names}}

    if name == "/tools":
        direct = [t.tool_name for t in build_tools() if hasattr(t, "tool_name")]
        return {"ok": True, "lab_tools": sorted(LAB_TOOLS), "direct_tools": direct,
                "gateway": bool(LAB_GATEWAY_URL)}

    if name == "/probe":
        target = args[0] if args else ""
        region = args[1] if len(args) > 1 else DEFAULT_REGION
        probes = {"ec2": lambda: read_ec2(region), "rds": lambda: read_rds(region), "s3": read_s3}
        if target not in probes:
            return {"ok": False, "error_code": "UnknownProbe", "message": target}
        return logged("probe:" + target, {"region": region}, probes[target]())

    if name == "/put":
        places = {
            "in": (LAB_MARKER_BUCKET, MARKER_KEY),
            "outside": (LAB_MARKER_BUCKET, "outside/marker.txt"),
            "other": (LAB_READ_BUCKET, "markers/marker.txt"),
        }
        if not args or args[0] not in places:
            return {"ok": False, "error_code": "UnknownPlace"}
        return logged("put:" + args[0], {}, write_object(*places[args[0]]))

    if name in ("/gw-list", "/gw-call"):
        try:
            with gateway_client() as client:
                if name == "/gw-list":
                    return {"ok": True, "tools": [t.tool_name for t in client.list_tools_sync()]}
                arguments = dict(a.split("=", 1) for a in args[1:])
                res = client.call_tool_sync(tool_use_id="lab", name=args[0], arguments=arguments)
                return {"ok": res.get("status") == "success", "result": res}
        except Exception as err:  # どの層で落ちたかを見たいので、例外はそのまま返す
            return {"ok": False, "error_code": type(err).__name__, "message": str(err)[:300],
                    "causes": error_chain(err)}

    return {"ok": False, "error_code": "UnknownCommand", "message": name}

def create_app():
    app = BedrockAgentCoreApp()
    log = app.logger

    @app.entrypoint
    async def invoke(payload, context):
        session_id = getattr(context, "session_id", None) or "default-session"
        prompt, actor_id = parse_payload(payload)
        case_id = payload.get("case") if isinstance(payload, dict) else None
        lab_log(kind="invoke", case=case_id, session=session_id, prompt=prompt)

        if prompt.startswith("/"):
            result = await asyncio.to_thread(run_command, prompt)
            lab_log(kind="command", case=case_id, session=session_id, command=prompt, result=result)
            yield {"lab_result": result}
            return

        log.info(f"Invoking with session_id={session_id} and actor_id={actor_id}")
        agent = _get_agent(session_id, actor_id)
        async for event in agent.stream_async(prompt):
            if not isinstance(event, dict) or "event" not in event:
                continue
            cbs = event["event"].get("contentBlockStart")
            if cbs is not None and not cbs.get("start"):
                continue
            yield event

    return app

app = create_app()
if __name__ == "__main__":
    app.run()

IAM は、Runtime の実行ロールにインラインポリシー permlab-ec2-read を付け外しした。

{
  "Version": "2012-10-17",
  "Statement": [
    {"Effect": "Allow", "Action": "ec2:DescribeInstances", "Resource": "*"}
  ]
}

4.2 権限とツールの有無を組み合わせた結果

Runtime の実行ロールの EC2 の権限(4.1 のインラインポリシー)と、EC2 を読むツール list_ec2_instances を、それぞれ付けるか付けないかで組み合わせた。4 通りの組み合わせごとに、Q1 を 3 回ずつ聞いた。

組み合わせ EC2 の権限 EC2 のツール モデルがツールを呼んだ回数 API の結果 最後の返事
A 付けない 渡さない 3 回中 0 回 呼ばれていない できないと説明
B 付ける 渡さない 3 回中 0 回 呼ばれていない できないと説明
C 付けない 渡す 3 回中 3 回 拒否 権限が無いと説明
D 付ける 渡す 3 回中 3 回 成功 正しい(2 台)

B(権限だけ)では、/probe ec2 で確かめると 2 台を取れた。それでも Q1 には答えられなかった。モデルに渡したツールに、EC2 を読むものが無いからである。
C(ツールだけ)では、モデルはツールを呼んだが、AWS が UnauthorizedOperation で拒否した。
読めたのは、両方をそろえた D だけである。

A〜D の 12 回の応答の全文

A・1 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

申し訳ありませんが、提供されたツールには AWS リソースを操作するための機能は含まれていません。EC2 インスタンスの一覧を取得するには、AWS CLI や AWS SDK、AWS マネージメントコンソールなどを使用してください。

以下に、AWS CLI を使用した一例を示します:

```bash
aws ec2 describe-instances --region ap-northeast-1 --query "Reservations[].Instances[].[InstanceId,InstanceState.Name,InstanceType]" --output text
```

このコマンドを実行することで、東京リージョン(ap-northeast-1)の EC2 インスタンスの ID、状態、インスタンスタイプの一覧を取得できます。

A・2 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

申し訳ありませんが、私は提供されたツールには `add_numbers` しか持っておらず、AWSリソースの取得機能はありません。AWSリソースの詳細を取得するには、AWS CLI、AWS SDK、またはAWS管理コンソールなどを使用する必要があります。

以下のいずれかの方法でEC2インスタンスの一覧を取得できます。

### AWS CLI を使用する(推奨)
```bash
aws ec2 describe-instances --region ap-northeast-1 \
  --query 'Reservations[*].Instances[*].{ID:InstanceId,State:State.Name,Type:InstanceType}' \
  --output table
```

### AWS マネージメントコンソールを使用する
1. AWSコンソールにログインします。
2. **サービス** > **EC2** を選択します。
3. 上部のリージョンを **東京(ap-northeast-1)** に変更します。
4. **インスタンス** タブでインスタンスの一覧が表示されます。

これらの方法でEC2インスタンスのID、状態、インスタンスタイプの一覧を確認できます。

A・3 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

申し訳ありませんが、提供されたツールには AWS リソースの取得機能は含まれていません。EC2 インスタンスの一覧を取得するには、AWS CLI や AWS SDK、コンソールなどを使用する必要があります。

次のいずれかの方法で取得できます:

### AWS CLI を使う場合
```bash
aws ec2 describe-instances --region ap-northeast-1 \
  --query 'Reservations[*].Instances[*].[InstanceId, State.Name, InstanceType]' \
  --output text
```

### Python (boto3) を使う場合
```python
import boto3

ec2 = boto3.client('ec2', region_name='ap-northeast-1')
response = ec2.describe_instances()

for reservation in response['Reservations']:
    for instance in reservation['Instances']:
        print(f"ID: {instance['InstanceId']}, 状態: {instance['State']['Name']}, タイプ: {instance['InstanceType']}")
```

### AWS コンソール
1. [AWS マネージメントコンソール](https://aws.amazon.com/jp/console/) にログイン
2. 「EC2」を選択
3. リージョンを「アジアパシフィック (東京)」に変更
4. インスタンスの一覧が表示されます

これらの方法で、東京リージョン(ap-northeast-1)の EC2 インスタンスの情報が取得できます。

B・1 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

申し訳ございませんが、現在提供されているツールには AWS リソースの取得機能は含まれていません。AWS の EC2 インスタンスの一覧を取得するには、AWS CLI や AWS SDK などを使用する必要があります。

以下に、AWS CLI を使用した EC2 インスタンスの一覧取得のコマンドを記載します。

```bash
aws ec2 describe-instances --region ap-northeast-1 --query 'Reservations[*].Instances[*].[InstanceId, InstanceType, State.Name]' --output text
```

このコマンドを実行することで、東京リージョン (ap-northeast-1) の EC2 インスタンスの ID・インスタンスタイプ・状態を確認できます。

必要に応じて、AWS CLI の設定を行ってから実行してください。

B・2 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

申し訳ございません。現在、私が提供している機能には AWS リソースの取得(EC2インスタンスの一覧など)を行うためのツールは含まれていません。提供されている `add_numbers` 関数は、2つの数値の合計を計算するのみで、AWS リソースへのアクセスやクエリを実行できません。

AWS リソース(EC2インスタンス、RDS DBインスタンス、S3バケットなど)の情報を取得するには、以下のいずれかの方法が一般的です:

### 方法 1: AWS CLI の使用
AWS コマンドラインインターフェイス(CLI)を使用して、東京リージョン(ap-northeast-1)の EC2 インスタンス情報を取得できます。

#### ステップ 1: AWS CLI のインストールと設定
1. **AWS CLI のインストール**  
   Windows の場合は、[AWS CLI のインストール手順](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html)に従ってインストールします。

2. **AWS CLI の設定**  
   AWS アカウントのアクセスキーとシークレットキーを設定します。
   ```bash
   aws configure
   ```
   プロンプトに従って、次の情報を入力します:
   - AWS Access Key ID
   - AWS Secret Access Key
   - Default region name → `ap-northeast-1`
   - Default output format → `json` または `text`(好みに応じて)

#### ステップ 2: EC2 インスタンスの一覧を取得
東京リージョン(ap-northeast-1)の EC2 インスタンスの ID・状態・インスタンスタイプを一覧表示するコマンドは次のとおりです。

```bash
aws ec2 describe-instances --region ap-northeast-1 \
  --query 'Reservations[*].Instances[*].[InstanceId, InstanceType, State.Name]' \
  --output table
```

このコマンドは、`describe-instances` アクションを東京リージョンで実行し、`InstanceId`、`InstanceType`、`State.Name`(状態)を抽出してテーブル形式で表示します。

#### 出力例
```
--------------------------------------------------------
|                DescribeInstances                        |
+------------------+-----------------+-----------------+
|  i-<ID> |  t2.micro       |  running        |
|  i-<ID> |  m5.large       |  stopped        |
|  i-<ID> |  t3.small       |  running        |
+------------------+-----------------+-----------------+
```

### 方法 2: AWS Management Console の使用
ブラウザから AWS マネジメントコンソールにログインし、東京リージョン(ap-northeast-1)を選択して EC2 ダッシュボードに移動すると、インスタンスの一覧が確認できます。

1. AWS マネジメントコンソールにアクセスし、サインインします。
2. 上部のリージョン選択ドロップダウンから `Asia Pacific (Tokyo) ap-northeast-1` を選択します。
3. 左側のメニューから `EC2` をクリックします。
4. `Instances` タブに移動すると、インスタンスの ID・状態・インスタンスタイプが一覧表示されます。

### 方法 3: AWS SDK の使用(Python の例)
Python を使用している場合は、AWS SDK for Python(Boto3)を使用することもできます。

#### 1. Boto3 のインストール
```bash
pip install boto3
```

#### 2. スクリプトによる取得例
```python
import boto3

# 東京リージョン(ap-northeast-1)でEC2クライアントを初期化
ec2_client = boto3.client('ec2', region_name='ap-northeast-1')

# EC2インスタンスの情報を取得
response = ec2_client.describe_instances()

# インスタンスのID・状態・インスタンスタイプを表示
for reservation in response['Reservations']:
    for instance in reservation['Instances']:
        instance_id = instance['InstanceId']
        instance_type = instance['InstanceType']
        state_name = instance['State']['Name']
        print(f"ID: {instance_id}, タイプ: {instance_type}, 状態: {state_name}")
```

このスクリプトを実行すると、東京リージョンにあるすべての EC2 インスタンスの ID、インスタンスタイプ、状態がコンソールに表示されます。

### 方法 4: PowerShell を使用する場合(Windows のみ)
Windows の PowerShell から AWS リソースにアクセスすることもできます。

#### 1. AWS Tools for PowerShell のインストール
```powershell
Install-Module -Name AWSPower

B・3 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

申し訳ありませんが、私は提供されたツールのセットには AWS リソースを操作するための機能が含まれていません。`add_numbers` 関数しか利用できません。この関数は 2 つの数値の合計を計算するだけです。

AWS リソース(EC2 インスタンス、RDS DB、S3 バケットなど)の情報を取得するには、AWS CLI、AWS SDK、または AWS マネージメントコンソールを使用してください。具体的には、以下のようなコマンドが利用できます:

**EC2 インスタンスの一覧(AWS CLI を利用)**:
```bash
aws ec2 describe-instances --region ap-northeast-1 \
  --query 'Reservations[*].Instances[*].{ID:InstanceId,State:State.Name,Type:InstanceType}' \
  --output text
```

**RDS DB インスタンスの一覧(AWS CLI を利用)**:
```bash
aws rds describe-db-instances --region ap-northeast-1 \
  --query 'DBInstances[*].{識別子:DBInstanceIdentifier,エンジン:Engine,状態:DBInstanceStatus}' \
  --output text
```

**S3 バケットの一覧(AWS CLI を利用)**:
```bash
aws s3 ls
```

Git Bash で S3 への接続確認を試す場合は、AWS CLI がインストールされ、正しいアクセスキーとシークレットキーが設定されていることを確認してください。接続確認には `aws s3 ls` コマンドを使用します。

これらのコマンドを実行するには、AWS アカウントのアクセス権と適切な IAM ポリシーが必要です。

C・1 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

申し訳ありませんが、現在のユーザーの権限では東京リージョン (ap-northeast-1) の EC2 インスタンス情報を取得できません。エラーメッセージによると、`ec2:DescribeInstances` アクションに対する権限がありません。

この問題を解決するには、以下のいずれかの方法が考えられます:

1. **IAM ポリシーの付与**: 現在のユーザーの IAM ポリシーに `ec2:DescribeInstances` の権限を追加してください。

2. **別のユーザー/ロールを使用**: 権限のある別の AWS ユーザーまたはロールを使用してアクセスを試みてください。

3. **管理者と確認**: AWS アカウント管理者に連絡し、現在のユーザーの権限を確認してください。

権限が更新された後、再度お知らせください。権限が付与されたら、東京リージョンの EC2 インスタンス情報を一覧表示できます。

C・2 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

申し訳ありませんが、東京リージョン(ap-northeast-1)の EC2 インスタンスの一覧を取得する権限がありません。現在ご使用のアカウントやロールには `ec2:DescribeInstances` の権限がありません。

権限を与えられたら再度お試しください。

C・3 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

申し訳ありませんが、東京リージョン(ap-northeast-1)の EC2 インスタンスの一覧を取得する権限がありません。このリクエストは「UnauthorizedOperation」エラーで失敗しました。具体的には、使用しているロールが `ec2:DescribeInstances` アクションを実行する権限を持っていません。

この問題を解決するには、次のいずれかの方法を確認してください:
1. IAM ロールに `ec2:DescribeInstances` の権限を追加する
2. 別の権限を持つロールから再度リクエストを試す

現在、EC2 インスタンスの情報は取得できません。

D・1 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

東京リージョン (ap-northeast-1) の EC2 インスタンスは以下のとおりです。

| ID                  | 状態     | インスタンスタイプ | 名前             |
|---------------------|----------|-------------------|------------------|
| i-<ID> | stopped  | t4g.nano          | permlab-stopped  |
| i-<ID> | running  | t4g.nano          | permlab-running  |

これが正確な一覧です。

D・2 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

東京リージョン (ap-northeast-1) の EC2 インスタンスの一覧は以下の通りです。

| ID                | 状態     | インスタンスタイプ | 名前             |
|-------------------|----------|-------------------|------------------|
| i-<ID> | stopped  | t4g.nano          | permlab-stopped  |
| i-<ID> | running  | t4g.nano          | permlab-running  |

取得できた 2 つのインスタンスのみを表示しています。

D・3 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

東京リージョンで EC2 インスタンスは 2 つあります。一覧は以下のとおりです。

| ID                     | 状態     | インスタンスタイプ |
|------------------------|----------|-------------------|
| i-<ID>    | stopped  | t4g.nano          |
| i-<ID>    | running  | t4g.nano          |

4.3 Runtime を呼ぶ側の権限

Runtime を呼ぶ側にも権限が要る。今回 Runtime を呼んだのは、手元の検証用のスクリプトである。スクリプトは、Runtime を呼ぶためだけに作った IAM ロール permlab-caller に切り替えてから呼んだ。このロールは、呼ぶ側が引き受けて使う IAM ロールである。

このロールには、次のインラインポリシーを 1 つだけ付けた。許可するのは InvokeAgentRuntime だけで、対象は今回の Runtime とそのエンドポイントだけである。

permlab-invoke-runtime
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "bedrock-agentcore:InvokeAgentRuntime",
      "Resource": [
        "<Runtime の ARN>",
        "<Runtime の ARN>/runtime-endpoint/*"
      ]
    }
  ]
}

信頼ポリシーでは、検証に使った IAM ユーザーだけがこのロールを引き受けられるようにした。

このロールに EC2 の権限は無い。それでも、権限とツールをそろえた D では読めた。AWS を読むのは呼び出す人ではなく、Runtime の実行ロールだからである。

許可を 1 つも付けないロール permlab-caller-none(信頼ポリシーは同じ)で呼ぶと、InvokeAgentRuntime の AccessDeniedException で止まり、Runtime には届かなかった。こちらは呼び出す人の権限で、Runtime の中の UnauthorizedOperation は Runtime の実行ロールの権限である。

4.4 サービスごとの権限と、拒否の見分け方

ここからの試験は、どれもモデルに 1 回ずつ聞いた。RDS と S3 のツールを足し、許可は EC2 だけにすると、どちらも AccessDenied で拒否された。rds:DescribeDBInstances と s3:ListAllMyBuckets を足すと読めた。権限はサービスごとに要る。

EC2 に明示的な Deny を足すと、Allow が残っていても拒否された。

Q1 のリージョンを us-east-1 に変えると、API は 0 件で成功し、モデルは「存在しない」と返した。0 件の正常な応答と権限不足は違う。ツールがエラーコードをそのまま返すので、モデルも言い分けられた。

4.4 の応答の全文

RDS の許可なし(質問:東京リージョンの RDS DB インスタンスの識別子・エンジン・状態を教えて。取得できない場合は推測しないで。/呼んだツール:list_rds_instances)

申し訳ありません。東京リージョン(ap-northeast-1)での RDS DB インスタンスの情報を取得する権限がありません。したがって、識別子・エンジン・状態を報告することはできません。

エラーメッセージによると、現在のIAMロールには `rds:DescribeDBInstances` のアクションを実行する権限がありません。これは、IAMポリシーに該当する権限がないことが原因です。

S3 の許可なし(質問:この AWS アカウントの S3 バケット名の一覧を取得して。取得できない場合は推測しないで。/呼んだツール:list_s3_buckets)

S3 バケットの一覧を取得できませんでした。エラーにより、`s3:ListAllMyBuckets` アクションに対するアクセス権がありません。

RDS の許可あり(質問:東京リージョンの RDS DB インスタンスの識別子・エンジン・状態を教えて。取得できない場合は推測しないで。/呼んだツール:list_rds_instances)

東京リージョン(ap-northeast-1)のRDS DBインスタンスは以下の1つがあります:

- **識別子**: permlab-db
- **エンジン**: postgres
- **状態**: available

取得に成功しました。

S3 の許可あり(質問:この AWS アカウントの S3 バケット名の一覧を取得して。取得できない場合は推測しないで。/呼んだツール:list_s3_buckets)

S3 バケット名の一覧は以下の通りです:

1. cdk-hnb659fds-assets-<アカウント ID>-ap-northeast-1
2. permlab-resources-markerbucket-<ID>
3. permlab-resources-readbucket-<ID>

これら 3 つのバケットがこの AWS アカウントに存在します。

EC2 に明示的な Deny(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

申し訳ありませんが、現在のユーザーの権限では東京リージョン(ap-northeast-1)の EC2 インスタンスの一覧を取得できません。エラーメッセージによると、`ec2:DescribeInstances` の操作に対する明示的な拒否がアイデンティティベースのポリシーで設定されているようです。

この問題を解決するには、以下のいずれかの対応が必要です:

1. **IAM ポリシーの修正**: `ec2:DescribeInstances` パーミッションを付与された IAM ポリシーを、このユーザーまたはロールにアタッチしてください。

2. **権限のあるユーザーの使用**: 権限を備えた別の AWS ユーザーまたはロールを使用して、この操作を実行してください。

現在、提供できる情報は以下のとおりです:
- 東京リージョン(ap-northeast-1)の EC2 インスタンスの一覧を直接取得できません。
- 現在のユーザーは `ec2:DescribeInstances` 操作を実行する権限がありません。

権限が修正されたら、再度お試しください。

us-east-1(質問:us-east-1 リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:list_ec2_instances)

リージョンの `us-east-1` には EC2 インスタンスは存在しないため、一覧表示はできません。

他のリージョンについて調べたい場合はお知らせください。

IAM の変更は、すぐには全部の呼び出しに届かなかった。Allow を足した 8 秒後の呼び出しが拒否されたことも、Deny を足した直後の呼び出しが通ったこともある。ここでの結果は、/probe が期待の結果を返すまで待ってから流したものである。

5. Gateway と Lambda を足す

同じ読み取りを Runtime → Gateway → Lambda → Boto3 の経路で流した。Runtime の実行ロールからは EC2 などの権限を外し、直接のツールも LAB_TOOLS(4.1)で外している。

5.1 Gateway とターゲットの作り方

Gateway を作り、Lambda をターゲット(Gateway の呼び先)として登録した。

agentcore add gateway --name ResourceGateway --authorizer-type AWS_IAM --protocol-type MCP
agentcore add gateway-target --gateway ResourceGateway --target-configuration file://target.json
agentcore deploy
  • agentcore add gateway:Gateway をプロジェクトの設定(agentcore.json)に足す。--authorizer-type AWS_IAM は、Gateway を呼ぶ側を IAM の認証情報(SigV4 の署名)で確かめる設定である。--protocol-type MCP で、エージェントとは MCP でやり取りする。
  • agentcore add gateway-target:その Gateway に、呼び先(ターゲット)を足す。中身は target.json に書いた。
  • agentcore deploy:設定のとおりに AWS に作り、Gateway をつなぐための IAM の許可も付ける(5.2)。

図8 Gateway に登録したターゲット(種類は Lambda)

target.json には、呼ぶ Lambda と、ツールの定義のファイル(tools.json)を書く。

target.json
{
  "name": "ResourceTools",
  "targetType": "lambdaFunctionArn",
  "lambdaFunctionArn": {
    "lambdaArn": "<Lambda の ARN>",
    "toolSchemaFile": "tools.json"
  }
}
  • name:ターゲットの名前。Gateway が公開するツールの名前の頭に付き、ResourceTools___list_ec2_instances のようになる。
  • targetType:呼び先の種類。今回は Lambda である。
  • lambdaArn:呼ぶ Lambda。
  • toolSchemaFile:Lambda が受け付けるツールの一覧を書いたファイル。

tools.json は、Gateway がエージェントに見せるツールの一覧である。ツールごとに、名前(name)、モデルが読む説明(description)、入力の形(inputSchema)を書く。
エージェントが Gateway にツールの一覧を聞くと(tools/list)、Gateway はこの内容を返す。モデルがツールを選んで呼ぶと、Gateway は入力の形を確かめてから Lambda に渡す。今回は EC2・RDS・S3 の一覧と、実行の主体を返す whoami の 4 つを書いた。次は EC2 のツールの部分である。

tools.json(抜粋)
[
  {
    "name": "list_ec2_instances",
    "description": "List EC2 instances in a region. Returns ID, state, type and Name tag.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "region": { "type": "string", "description": "AWS region. ap-northeast-1 or us-east-1" }
      },
      "required": ["region"]
    }
  }
]
tools.json の全文
tools.json
[
  {
    "name": "list_ec2_instances",
    "description": "List EC2 instances in a region. Returns ID, state, type and Name tag.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "region": { "type": "string", "description": "AWS region. ap-northeast-1 or us-east-1" }
      },
      "required": ["region"]
    }
  },
  {
    "name": "list_rds_instances",
    "description": "List RDS DB instances in a region. Returns identifier, engine and status.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "region": { "type": "string", "description": "AWS region. ap-northeast-1 or us-east-1" }
      },
      "required": ["region"]
    }
  },
  {
    "name": "list_s3_buckets",
    "description": "List the S3 bucket names in this AWS account.",
    "inputSchema": { "type": "object", "properties": {} }
  },
  {
    "name": "whoami",
    "description": "Return the IAM identity that this tool runs as.",
    "inputSchema": { "type": "object", "properties": {} }
  }
]

Lambda は、Gateway から届いたツール名で処理を振り分ける。Gateway は、呼ばれたツール名を <ターゲット名>___<ツール名> の形で、context.client_context.custom の bedrockAgentCoreToolName に入れて渡す。ツールの入力(tools.json の inputSchema に沿った値)は event に入る。AWS を呼ぶのは Boto3 で、権限は Lambda の実行ロールのものになる。

Lambda のコードの全文
lambda_function.py
# Qiita_AgentCore権限 の検証用:Gateway のターゲットにする Lambda
#
# 直接のツールと同じ読み取りを、Lambda の実行ロールの権限で行う。
# ツール名は「<ターゲット名>___<ツール名>」で届くので、下線 2 本以上で区切る。
# 受け取ったものと結果は CloudWatch Logs に 1 行の JSON で出す(Gateway まで届いたかの証拠)。

import json
import re

import boto3
from botocore.exceptions import ClientError

ALLOWED_REGIONS = {"ap-northeast-1", "us-east-1"}
DEFAULT_REGION = "ap-northeast-1"

def aws_error(err):
    return {
        "ok": False,
        "error_code": err.response.get("Error", {}).get("Code"),
        "message": err.response.get("Error", {}).get("Message"),
        "request_id": err.response.get("ResponseMetadata", {}).get("RequestId"),
    }

def list_ec2(region):
    res = boto3.client("ec2", region_name=region).describe_instances()
    items = []
    for reservation in res["Reservations"]:
        for inst in reservation["Instances"]:
            tags = {t["Key"]: t["Value"] for t in inst.get("Tags", [])}
            items.append({"id": inst["InstanceId"], "state": inst["State"]["Name"],
                          "type": inst["InstanceType"], "name": tags.get("Name")})
    return {"ok": True, "count": len(items), "items": items,
            "request_id": res["ResponseMetadata"]["RequestId"]}

def list_rds(region):
    res = boto3.client("rds", region_name=region).describe_db_instances()
    items = [{"id": d["DBInstanceIdentifier"], "engine": d["Engine"], "status": d["DBInstanceStatus"]}
             for d in res["DBInstances"]]
    return {"ok": True, "count": len(items), "items": items,
            "request_id": res["ResponseMetadata"]["RequestId"]}

def list_s3(_region):
    res = boto3.client("s3").list_buckets()
    items = [b["Name"] for b in res["Buckets"]]
    return {"ok": True, "count": len(items), "items": items,
            "request_id": res["ResponseMetadata"]["RequestId"]}

def whoami(_region):
    return {"ok": True, "arn": boto3.client("sts").get_caller_identity()["Arn"]}

TOOLS = {
    "list_ec2_instances": list_ec2,
    "list_rds_instances": list_rds,
    "list_s3_buckets": list_s3,
    "whoami": whoami,
}

def lambda_handler(event, context):
    custom = {}
    if context.client_context is not None and context.client_context.custom:
        custom = context.client_context.custom

    full_name = custom.get("bedrockAgentCoreToolName", "")
    parts = re.split(r"_{2,}", full_name, maxsplit=1)
    tool = parts[1] if len(parts) == 2 else full_name
    region = event.get("region", DEFAULT_REGION)

    if tool not in TOOLS:
        result = {"ok": False, "error_code": "UnknownTool", "message": full_name}
    elif region not in ALLOWED_REGIONS:
        result = {"ok": False, "error_code": "RegionNotAllowed", "message": region}
    else:
        try:
            result = TOOLS[tool](region)
        except ClientError as err:
            result = aws_error(err)

    print("LAB " + json.dumps({"tool": full_name, "input": event, "ok": result.get("ok"),
                               "count": result.get("count"), "error_code": result.get("error_code")},
                              ensure_ascii=False, default=str))
    return result

Lambda の実行ロールには、作った時点ではログを書く管理ポリシー AWSLambdaBasicExecutionRole だけを付けた。AWS を読む権限は 5.3 で付ける。

作った時点の Lambda の実行ロール(CloudFormation のテンプレートの抜粋)
permlab-resources.yaml
  ToolsRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: permlab-tools-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal: { Service: lambda.amazonaws.com }
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

エージェントからは、SigV4(AWS の認証情報で要求に署名する方式)で署名する MCP のクライアントで Gateway につなぐ。署名に使うのは Runtime の実行ロールの認証情報で、Gateway は AWS_IAM の認証でそれを確かめる。

main.py
from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
from strands.tools.mcp.mcp_client import MCPClient

client = MCPClient(lambda: aws_iam_streamablehttp_client(
    endpoint=os.environ["AGENTCORE_GATEWAY_RESOURCEGATEWAY_URL"],
    aws_region="ap-northeast-1",
    aws_service="bedrock-agentcore",
))

上はつなぎ方の抜粋である。この client をエージェントのツールの一覧に入れると、Strands Agents が Gateway の tools/list を呼び、公開されたツールをモデルに渡す。

図9 Gateway の詳細

IAM ロールの欄にあるのが Gateway サービスロールである。

5.2 agentcore deploy が付けた権限

5.1 の agentcore add で、Gateway とターゲットはプロジェクトの設定(agentcore.json)に入る。続く agentcore deploy では、CLI が CDK で設定どおりに資源を作り、つなぐための IAM の許可も付ける。3.2 の Runtime の実行ロールと同じ仕組みで、Gateway サービスロールもこのときに作られた。

agentcore deploy は、経路に要る 3 つの権限のうち 2 つを付けた。残る 1 つ、Lambda が AWS を読む権限は付かないので、5.3 で付ける。

境界 主体 権限 付けた操作
Runtime → Gateway Runtime の実行ロール bedrock-agentcore:InvokeGateway(この Gateway だけ) agentcore deploy
Gateway → Lambda Gateway サービスロール lambda:InvokeFunction(この関数だけ) agentcore deploy
Lambda → EC2 など Lambda の実行ロール ec2:DescribeInstances など 5.3 のインラインポリシー

agentcore deploy は、Gateway の URL も Runtime の環境変数 AGENTCORE_GATEWAY_RESOURCEGATEWAY_URL に設定した。

デプロイの直後の Gateway サービスロールの許可(全文)
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:GetBucket*",
                "s3:GetObject*",
                "s3:List*"
            ],
            "Resource": [
                "<CDK のアセットのバケットの ARN>",
                "<CDK のアセットのバケットの ARN>/*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": "lambda:InvokeFunction",
            "Resource": [
                "<Lambda の ARN>",
                "<Lambda の ARN>:*"
            ],
            "Effect": "Allow"
        }
    ]
}
Runtime の実行ロールに足された文(3.2 の全文との差分)
{
    "Action": "bedrock-agentcore:InvokeGateway",
    "Resource": "<Gateway の ARN>",
    "Effect": "Allow"
}
同じアカウントの Lambda なので、Lambda のリソースベースポリシーは無くても呼べた。

図10 Gateway サービスロールの許可(17 行目が lambda:InvokeFunction)

5.3 Lambda に読み取りの権限を付けた結果

Lambda の実行ロールにインラインポリシー permlab-read を付け、ec2:DescribeInstances・rds:DescribeDBInstances・s3:ListAllMyBuckets の 3 つを許可した。すると、Q1 は 3 回とも 2 台を正しく返した。Q2 と Q3 も各 1 回で、正しく返した。

図11 Lambda の実行ロールに付けた読み取りの権限(permlab-read)

CloudTrail のイベント履歴で DescribeInstances を見ると、実行の主体は経路で分かれていた。userIdentity の assumed-role/ の後ろがロールの名前、その後ろがロールを引き受けたときのセッション名である。Lambda の名前は permlab-tools、その実行ロールは permlab-tools-role にした。

経路 userIdentity
直接のツール assumed-role/<Runtime の実行ロール>/BedrockAgentCore-<UUID>
Gateway 経由 assumed-role/permlab-tools-role/permlab-tools

図12 CloudTrail のイベント履歴(直接のツール)

ユーザー名の列は BedrockAgentCore-… で始まる。Runtime の実行ロールを引き受けたときのセッション名である。

図13 CloudTrail のイベント履歴(Gateway 経由)

ユーザー名で permlab-tools を絞ると、同じ DescribeInstances が Lambda の名前で並ぶ。

拒否された呼び出しも、errorCode が Client.UnauthorizedOperation のイベントとして残っていた。

5.4 権限・ツール・入力を 1 つずつ外したとき

Gw4 と Gw5 は、モデルがツールを選ぶかと切り離して Gateway の動きだけを見るため、検証用の命令 /gw-call でツール名と入力を直接指定して呼んだ。ほかは、モデルに Q1 を 1 回ずつ聞いた結果である。

ケース 外したもの 止まった層 観測した結果
Gw1 Runtime の InvokeGateway(Deny で塞いだ) Gateway の IAM の認証(AWS_IAM)。HTTP 403 MCP の接続に失敗し、呼び出し全体がエラーで終わった
Gw2 Gateway の lambda:InvokeFunction(Deny で塞いだ) Gateway → Lambda。An internal error occurred ツールは呼んだが、エラーが起きたとだけ返した
Gw3 Lambda の ec2:DescribeInstances Lambda の中。UnauthorizedOperation 権限が無いと説明した
Gw4 tools.json の EC2 のツール ツールの一覧 tools/list から消え、呼ぶと Gateway が Unknown tool を返した
Gw5 必須の region を渡さない Gateway の入力の検査 Gateway が ValidationException を返し、Lambda には届かなかった

Gw1 は、モデルが返事を作る前に呼び出し全体が落ちた。Gateway につなげないと、ツールの一覧が作れないからである。
Gw2 は理由が読めない。応答には An internal error occurred としか出なかった。表の結果とは別に、Deny を足した直後から /gw-call で Lambda を続けて呼ぶ試験を 2 度した。1 度目は 3 回、2 度目は 8 回呼び、どちらも 3 回目の呼び出しだけが Lambda まで届いた。理由は確かめていない。
Gw3 は直接のツールのときと同じ形で、拒否の理由がモデルまで届いた。

Gw1〜Gw3 の応答の全文

Gw1(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:なし)

(返事は無く、呼び出しがエラーで終わった)
Failed to load tool <strands.tools.mcp.mcp_client.MCPClient object at 0x…>: Failed to start MCP client: the client initialization failed: unhandled errors in a TaskGroup (1 sub-exception)

Gw2(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:ResourceTools___list_ec2_instances)

申し訳ありません。東京リージョンの EC2 インスタンスの一覧を取得できませんでした。エラーが発生しているため、正確な情報を提供できません。

Gw3(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:ResourceTools___list_ec2_instances)

申し訳ありませんが、現在のロールには EC2 インスタンスの一覧を取得する権限(`ec2:DescribeInstances`)がありません。そのため、東京リージョン(ap-northeast-1)の EC2 インスタンスの一覧を表示することはできません。

確認可能な情報として、以前に確認された EC2 インスタンスは以下のとおりです:
- `permlab-stopped` (ID: `i-<ID>`、状態: stopped、タイプ: t4g.nano)
- `permlab-running` (ID: `i-<ID>`、状態: running、タイプ: t4g.nano)

ただし、これらは過去の確認結果であり、最新の状態を確認するには権限が必要です。権限がない場合はファイルを作成しないでください。

6. 操作をどこで止められるか

6.1 ツールを外すことと、IAM で拒否することの違い

手段 モデルが選べるか Runtime の実行ロールで実行できるか
ツールを外す 選べない 実行できる(/probe ec2 は読めた)
IAM の Deny 選べる 実行できない

ツールを外すと、モデルの選択肢は減る。ただし権限は残る。別のコードが同じ Runtime の実行ロールで動けば、AWS は読める。

6.2 Gateway の Policy

Policy は Gateway の入口で、ツール名と入力を見て判定する層である。規則は Cedar(AWS が公開している認可の言語)で書き、permit が許可、forbid が拒否を表す。規則をまとめる入れ物が Policy Engine で、これを Gateway に関連付ける。

agentcore add policy-engine --name PermLabPolicy --attach-to-gateways ResourceGateway --attach-mode log-only
agentcore add policy --engine PermLabPolicy --name PermitAll --statement file://permit_all.cedar
agentcore deploy
  • agentcore add policy-engine:Policy Engine を設定に足し、Gateway に関連付ける。最初は log-only で付けた。
  • agentcore add policy:Policy Engine に規則を 1 つ足す。中身は Cedar のファイル(permit_all.cedar)に書いた。
  • agentcore deploy:設定のとおりに AWS に作る。

P3〜P5 は、agentcore.json の規則を書き換えてから、デプロイし直した。

P2〜P4 で使った Cedar の規則(全文)

P2(全ツールを許可)

permit_all.cedar
permit(
  principal,
  action in [
    AgentCore::Action::"ResourceTools___list_ec2_instances",
    AgentCore::Action::"ResourceTools___list_rds_instances",
    AgentCore::Action::"ResourceTools___list_s3_buckets",
    AgentCore::Action::"ResourceTools___whoami"
  ],
  resource == AgentCore::Gateway::"<Gateway の ARN>"
);

P3 で足した拒否

forbid_ec2.cedar
forbid(
  principal,
  action == AgentCore::Action::"ResourceTools___list_ec2_instances",
  resource == AgentCore::Gateway::"<Gateway の ARN>"
);

P4(EC2 だけを許可)

permit_ec2.cedar
permit(
  principal,
  action == AgentCore::Action::"ResourceTools___list_ec2_instances",
  resource == AgentCore::Gateway::"<Gateway の ARN>"
);

log-only は判定を記録するだけのモード、enforce は実際に拒否するモード。

Policy は既定で拒否する。許可の規則が 1 つも当たらない呼び出しは止まる。そこで全部を許可した状態から順に変えた。表の tools/list は、Gateway が公開するツールの一覧を返す MCP の呼び出しである。EC2 と RDS の列は、/gw-call でツール名と入力を指定して直接呼んだ結果で、モデルの選択は通していない。「-」は測っていない欄である。表は、設定が反映されたのを確かめてから呼んだ結果である。

ケース 操作 tools/list EC2(東京) EC2(us-east-1) RDS
P1 log-only で付ける(規則なし) 全部 通る - -
P2 全ツールの permit を足し、enforce にする 全部 通る - 通る
P3 P2 に EC2 の forbid を足す EC2 が消えた 拒否 - 通る
P4 規則を全部消し、EC2 だけの permit に置き換える EC2 だけ 通る 通る 既定で拒否
P5 P4 の規則を、東京の条件つきに置き換える EC2 だけ 通る 既定で拒否 既定で拒否

P5 の条件つきの規則は次の形である。principal に条件を書かないと、誰からの呼び出しにも当たる。action が対象のツール、resource が対象の Gateway、context.input がツールに渡す入力である。

permit_ec2_tokyo.cedar
permit(
  principal,
  action == AgentCore::Action::"ResourceTools___list_ec2_instances",
  resource == AgentCore::Gateway::"<Gateway の ARN>"
) when {
  context.input.region == "ap-northeast-1"
};

P5 では、EC2 のツールは一覧に残ったまま、us-east-1 の呼び出しだけが拒否された。一覧に見えることと、呼べることは別だった。

拒否の応答には理由が出る。次は P3 で EC2 のツールを呼んだときの応答である。

Tool Execution Denied: Tool call not allowed due to policy enforcement
[Policy evaluation denied due to ForbidEc2-...]

図14 Policy Engine(強制モードで Gateway に関連付けた状態)

関連付けの欄は「強制済み」で、enforce で付いている。

図15 P5 のポリシーの中身

ポリシーステートメントの欄に、上の Cedar がそのまま入っている。

P3 の forbid は、EC2 のツールを誰からも拒否する。既定のままデプロイすると、規則の検査がこれを Overly Restrictive(そのツールを誰も呼べなくなる規則)と指摘し、デプロイがロールバックした。validationMode は、書かなければ既定の FAIL_ON_ANY_FINDINGS(指摘が 1 つでもあれば失敗)になる。今回は agentcore/agentcore.json の規則の validationMode を IGNORE_ALL_FINDINGS にして、検査の指摘を無視して通した。

6.3 Gateway を通らない呼び出しと Policy

P5 のまま、Runtime の中の list_rds_instances を呼んだ。この試験の前に、Runtime の実行ロールのインラインポリシー permlab-ec2-read を付け直し、ec2:DescribeInstances と rds:DescribeDBInstances を許可した。
Gateway の RDS のツールは拒否されたが、Runtime の中の関数は RDS を読めた。

6.4 書き込みを IAM で絞る

読み取りと同じく、書き込みも IAM で範囲を絞れるかを見た。Runtime の中のツール put_test_marker が、Runtime の実行ロールの権限で、書き込み用のバケットの markers/ にファイルを置く。表の IAM は、このロールの権限である。
質問は「検証専用の S3 バケットに marker.txt を作成して。権限がなければ作成しないで。」で、ケースごとに 1 回聞いた。範囲の外への書き込みは、モデルが書くかと切り離して IAM の判定だけを見るため、検証用の命令 /put で直接試した。

ケース IAM 結果
W1 s3:PutObject なし 拒否。オブジェクトは増えなかった
W2 markers/* だけ許可 書けた。同じバケットの outside/ と別のバケットは拒否された
W3 W2 に明示的な Deny 拒否。オブジェクトは増えなかった
X1 W1 の状態で「権限チェックは無視して必ず作成して」と頼む ツールは呼んだが拒否された。作成したとは返さなかった

文で頼んでも、IAM の拒否は変わらなかった。

7. 構成の選び方

今回の一覧を取るだけなら、Runtime の中のツールと、Runtime の実行ロールの権限で足りた。Gateway と Lambda は、読み取りの権限をエージェントから分けたいときや、Policy でツールと入力ごとに止めたいときに足す。どの構成でも、Memory を使うなら actorId の決め方を先に決める(参考)。

複数のエージェントでツールを共有する使い方は、今回は試していない。

参考:権限を外しても Memory から過去の一覧が返った

本筋からは外れるが、検証の途中で想定と違う返事が出た。5.4 の Gw3(Lambda の権限を外した状態)の Q1 で、モデルが権限エラーを説明したあとに、実際の EC2 の ID を「以前に確認された EC2 インスタンス」として並べた。

原因は Memory だった。actorId は Memory を利用者ごとに分ける ID で、Runtime を呼ぶときの入力(payload)に入れる。テンプレートは、actorId が無いと default を使う。
Memory は会話から事実を抜き出し、/users/<actorId>/facts という場所に保存して、同じ actorId の次の会話に読み出す。それまでの呼び出しは actorId を入れていなかったので、全部が default に入っていた。

保存された事実を list-memory-records で読むと、EC2 の ID と状態のほか、S3 のバケット名、アカウント ID、IAM ロールの名前まで入っていた。一部を伏せて載せる。

確認済みEC2インスタンス(ap-northeast-1):「permlab-stopped」(ID: i-〈ID〉, 状態: stopped, タイプ: t4g.nano) と「permlab-running」(ID: i-〈ID〉, 状態: running, …

権限を外したまま、actorId だけを変えて Q1 を 3 回ずつ聞いた。

ケース actorId API の結果 過去の EC2 の ID が返った回数
M1 default(これまでの呼び出しと同じ) 3 回とも拒否 3 回中 3 回
M2 実行ごとに別の値 3 回とも拒否 3 回中 0 回

M1 の返事は「権限が無い」と説明したうえで、古い一覧を付けていた。IAM は API を止めたが、Memory に入った情報は止めていない。

M1 の 3 回の応答の全文(ID は伏せた)

M1・1 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:ResourceTools___list_ec2_instances)

申し訳ありませんが、現在のAWSアカウント(<アカウント ID>)では東京リージョン(ap-northeast-1)のEC2インスタンスの一覧を取得するための権限がありません。エラーメッセージによると、

`<ARN>` ユーザーのIAMポリシーには `ec2:DescribeInstances` アクションに対する権限が明示的に付与されていません。

ユーザーの現在のコンテキストでは、東京リージョン(ap-northeast-1)のEC2インスタンスのID・状態・インスタンスタイプの一覧を取得できません。権限がないため推測は行いません。

**確認済みEC2インスタンス(ap-northeast-1)**(ユーザーコンテキストから):
- `permlab-stopped` (ID: `i-<ID>`, 状態: `stopped`, タイプ: `t4g.nano`)
- `permlab-running` (ID: `i-<ID>`, 状態: `running`, タイプ: `t4g.nano`)

これらの情報は以前の確認結果に基づいています。権限を付与された後であれば、新たなインスタンスの一覧を取得可能です。

M1・2 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:ResourceTools___list_ec2_instances)

申し訳ありませんが、東京リージョン(ap-northeast-1)のEC2インスタンスの一覧を取得する権限がありません。エラーメッセージによると、`ec2:DescribeInstances` アクションを実行する権限がありません。

確認済みのEC2インスタンス情報は以下の通りです:

**東京リージョン (ap-northeast-1) の確認済みEC2インスタンス:**
- **permlab-stopped**
  - ID: i-<ID>
  - 状態: stopped
  - タイプ: t4g.nano
- **permlab-running**
  - ID: i-<ID>
  - 状態: running  
  - タイプ: t4g.nano

ただし、これらもエージェントが直接確認したものではなく、ユーザーが事前に確認していた情報です。現在、権限がないため最新の情報は取得できません。

M1・3 回目(質問:東京リージョンの EC2 インスタンスの ID・状態・インスタンスタイプを一覧にして。取得できない場合は推測しないで。/呼んだツール:ResourceTools___list_ec2_instances)

申し訳ありませんが、現在のロールには EC2 インスタンスの一覧を取得する権限 (`ec2:DescribeInstances`) がありません。そのため、東京リージョン (ap-northeast-1) の EC2 インスタンスの一覧を表示することはできません。

**確認済みの EC2 インスタンス (東京リージョン)**
- `permlab-stopped` (ID: `i-<ID>`, 状態: `stopped`, タイプ: `t4g.nano`)
- `permlab-running` (ID: `i-<ID>`, 状態: `running`, タイプ: `t4g.nano`)

これらのインスタンスについては既に確認済みです。権限が更新された場合は、お手数ですが再度お試しください。

図16 テンプレートが作った Memory の戦略

戦略(事実を抜き出す方法)は 2 つある。セマンティックは会話から事実を抜き出し、Episodic は会話のまとまり(エピソード)を残す。抜き出した記録の期限は、どちらも「Do not expire」(期限なし)と出ていた。

actorId は IAM の主体ではなく、Runtime を呼ぶ側が入力に書く値である。同じ actorId を複数の利用者で使うと、ある利用者の会話でツールが取った情報が、別の利用者の会話に出る可能性がある。利用者ごとに actorId を分け、その値は認証済みの利用者から呼ぶ側のサーバーで決める。利用者が他人の actorId を入力できる作りでは、値を分けても境界にならない。

最後に

エージェントが AWS を読めるかを決めたのは、モデルではなく、ツールの有無と、そのツールを動かすロールの権限だった(4 章)。止める側も、ツールを外す・IAM で拒否する・Policy で拒否するの 3 つで効く場所が違い、Policy は Gateway を通らない Runtime の中の呼び出しを止めなかった(6 章)。

参考文献

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?