Y_Yamashitaのブログ

勉強したことのアウトプット・メモが中心。記事の内容は個人の見解であり、所属組織を代表するものではありません。

Amazon Bedrock AgentCore Policyでツール利用を制限してみた

最近、Amazon Bedrock AgentCore を勉強中です。 概要は少しずつ理解できてきたので、実際に実装して慣れていきたいと思います。 これまでにMemoryとGatewayを触ってみたので、今回は、プレビュー中のPolicyを使って、ツール利用を制限してみます。

最初にお断り

上述通り、本ブログ執筆時点の2025年12月30日では、Policyはプレビュー版です。 そのため、今後仕様が頻繁に変わる可能性がありますので、予めご了承ください。

謝辞

前回のGatewayに引き続き、以下の記事を大変参考にさせていただきました。この場を借りてお礼を申し上げます。ありがとうございました。

qiita.com

AgentCore Policyとは

エージェントからAgentCore Gatewayへのリクエストを、ポリシーベースで細かく制御できる機能です。 詳細は以下の公式ドキュメントを参照ください。

docs.aws.amazon.com

今回試してみること

以前のブログで、Gateway経由で外部ツールのTavilyを使い、NBAチームの最新成績を回答できるか確認しました。

yuy-83.hatenablog.com

今回は、上記のGatewayにポリシーを適用し、特定のNBAチームに関する問い合わせのみブロックできるか確認してみます。

Policyの設定

それでは早速触ってみます。まずはPolicyを設定していきます。

ポリシーエンジンの作成

Policyの画面に移動し、「ポリシーエンジンを作成」ボタンを押します。


適当な名前と説明を入れて、「ポリシーエンジンを作成」ボタンを押します。

ポリシーの作成

最初にポリシーを作成すると、そのままポリシー作成画面に遷移します。
ポリシーはCedarというフォーマットで指定します。自然言語のプロンプト、フォーム、Cedarの直接編集、いずれかの方法で指定が可能です。 今回はプロンプトから作成してみます。

リソーススコープに、前回のブログで作成したGatewayを指定し、プロンプトに以下を入力します。

NBAチーム:オクラホマシティ・サンダーに関連する問い合わせに対しては、ツールを使用してはいけません。
それ以外の問い合わせに対しては、ツールの使用を許可します。

「Cedarを生成」ボタンを押してしばらく待つと、自動で二つのポリシーが作成されました。

上が許可ルールで、下が拒否ルール

ポリシーの具体的な中身は以下でした。(ARNは一部マスキングしています)

■許可ルール

permit(
  principal,
  action in [AgentCore::Action::"Tavily___TavilySearchPost",AgentCore::Action::"Tavily___TavilySearchExtract"],
  resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx"
) when {
  !((((context has input) && ((context.input) has query)) && (((context.input).query) like "*オクラホマシティ・サンダー*")) || (((context has input) && ((context.input) has query)) && (((context.input).query) like "*Oklahoma City Thunder*")))
};

■拒否ルール

forbid(
  principal,
  action in [AgentCore::Action::"Tavily___TavilySearchPost",AgentCore::Action::"Tavily___TavilySearchExtract"],
  resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx"
) when {
  (((context has input) && ((context.input) has query)) && (((context.input).query) like "*オクラホマシティ・サンダー*")) || (((context has input) && ((context.input) has query)) && (((context.input).query) like "*Oklahoma City Thunder*"))
};

これだけ見ると、許可ルールだけでも良いような気がしますね。。ともかく、「ポリシーを作成」ボタンを押します。

無事にポリシーが作成されたようです。

GatewayのサービスロールにIAMポリシー追加

作成されたポリシーをGatewayに関連付けるのですが、その前に、Gatewayのサービスロールにポリシーを追加します。 サービスロールの権限が不足していると、ポリシーの関連付けが失敗するためです。 今回検証なので、 BedrockAgentCoreFullAccess を付与します。

ポリシーとGatewayの関連付け

ポリシーの画面に戻り、「アソシエイトゲートウェイ」ボタンを押します。


Gatewayと強制モードを選択します。今回は実際にブロックされるところを確認したいので「強制を有効化」を選択します。「アソシエイトゲートウェイ」ボタンを押します。


Gatewayが関連付けられました。これで準備は完了です。

動作確認

Policyの準備が出来たので、動作確認をしていきます。

エージェント

動作確認に使用するエージェントは Gateway検証時のエージェントを流用します。
少しだけコードを改修して、チャットっぽくやり取りできるようにしました。

import os
import requests
from dotenv import load_dotenv
from mcp import stdio_client, StdioServerParameters
from strands import Agent
from strands.models.litellm import LiteLLMModel
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamablehttp_client

load_dotenv()

SYSTEM_PROMPT = """
学習していないデータについて質問された場合、必ずツールを使用して最新の情報を取得してください。
もし最新の情報を取得できない場合、憶測で回答せず、分からないと回答してください。
"""

def main():
    model = LiteLLMModel(
        client_args={
            "api_key": os.getenv("AZURE_API_KEY"),
            "api_base": os.getenv("AZURE_API_BASE"),
            "api_version": os.getenv("AZURE_API_VERSION"),
        },
        model_id=f"azure/{os.getenv('AZURE_DEPLOYMENT_NAME')}",
        params={
            "max_tokens": 1000,
            "temperature": 0.7,
        }
    )
    
    # Cognitoからアクセストークンを取得
    response = requests.post(
        os.getenv("COGNITO_TOKEN_URL"),
        data={
            "grant_type": "client_credentials",
            "client_id": os.getenv("COGNITO_CLIENT_ID"),
            "client_secret": os.getenv("COGNITO_CLIENT_SECRET"),
        },
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        timeout=10,
    )
    access_token = response.json().get("access_token")

    # MCPクライアントを作成
    gateway_client = MCPClient(
        lambda: streamablehttp_client(
            os.getenv("AGENT_CORE_GATEWAY_RESOURCE_URL"),
            headers={"Authorization": f"Bearer {access_token}"}
        )
    )
    
    agent = Agent(
        model=model,
        tools=[gateway_client],
        system_prompt=SYSTEM_PROMPT
    )
        
    print("チャットを開始します。終了するには 'exit' と入力してください。\n")
        
    while True:
        user_input = input("You: ")
        
        if user_input.lower() in ['exit', 'quit', '終了', 'q']:
            print("チャットを終了します。")
            break
        
        if not user_input.strip():
            continue
        
        try:
            agent(user_input)
            print()  # 改行を追加
        except Exception as e:
            print(f"エラーが発生しました: {e}")
            print()  # エラー時も改行を追加

if __name__ == "__main__":
    main()

エージェントに問い合わせ

それでは実際にエージェントに問い合わせてみます。

LAPTOP:~/ai-agent/azure-openai-agent$ uv run ./policy_test.py
チャットを開始します。終了するには 'exit' と入力してください。

You: 日本時間2025年12月29日時点の、NBAチーム:オクラホマシティ・サンダーのレギュラーシーズンの成績(勝敗)を教えてください。

Tool #1: Tavily___TavilySearchPost
tool execution failed
Traceback (most recent call last):
  File "/home/xxxxx/ai-agent/azure-openai-agent/.venv/lib/python3.12/site-packages/strands/tools/mcp/mcp_client.py", line 512, in call_tool_async
    call_tool_result: MCPCallToolResult = await asyncio.wrap_future(future)
                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xxxxx/ai-agent/azure-openai-agent/.venv/lib/python3.12/site-packages/strands/tools/mcp/mcp_client.py", line 746, in run_async
    return await invoke_event
           ^^^^^^^^^^^^^^^^^^
  File "/home/xxxxx/ai-agent/azure-openai-agent/.venv/lib/python3.12/site-packages/strands/tools/mcp/mcp_client.py", line 506, in _call_tool_async
    return await cast(ClientSession, self._background_thread_session).call_tool(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xxxxx/ai-agent/azure-openai-agent/.venv/lib/python3.12/site-packages/mcp/client/session.py", line 350, in call_tool
    result = await self.send_request(
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/xxxxx/ai-agent/azure-openai-agent/.venv/lib/python3.12/site-packages/mcp/shared/session.py", line 288, in send_request
    raise McpError(response_or_error.error)
mcp.shared.exceptions.McpError: Tool Execution Denied: Tool call not allowed due to policy enforcement [Policy evaluation denied due to policy_9a7go-kqobls3p5x]
その情報を取得することができませんでした。2025年12月29日時点のオクラホマシティ・サンダーの成績は分かりません。最新の情報は、NBA公式サイトやスポーツニュースをご確認ください。

You: では、日本時間2025年12月29日時点の、シェイ・ギルジャス・アレクサンダー選手の今シーズンの個人成績を教えてください。平均得点・平均リバウンド数・平均アシスト数が知りたいです。

Tool #2: Tavily___TavilySearchPost

Tool #3: Tavily___TavilySearchExtract
日本時間2025年12月29日時点での、シェイ・ギルジャス・アレクサンダー選手の今シーズン(2025-2026シーズン)の平均成績に関する具体的な情報は、取得できませんでした。ただし、彼は非常に効率的なシーズンを送っており、得点やアシスト面でのパフォーマンスがキャリア最高水準であるとの報告があります。また、彼のシュート成功率も向上しており、特にミッドレンジや3ポイントシュートで高い成功率を記録しているとされています。

詳細な個人成績(平均得点、平均リバウンド数、平均アシスト数)については、NBA公式サイトや統計データ提供サービスを直接ご確認ください。

You: 次に、日本時間2025年12月29日時点の、NBAチーム:サンアントニオ・スパーズのレギュラーシーズンの成績(勝敗)を教えてください。

Tool #4: Tavily___TavilySearchPost

Tool #5: Tavily___TavilySearchExtract
日本時間2025年12月29日時点でのサンアントニオ・スパーズのレギュラーシーズンの成績(勝敗)に関する情報を取得することができませんでした。NBA公式サイトやスポーツニュースの最新情報をご確認いただくことをお勧めします。
You: では、日本時間2025年12月29日時点の、ヴィクター・ウェンバンヤマ選手の今シーズンの個人成績を教えてください。平均得点・平均リバウンド数・平均アシスト数が知りたいです。

Tool #6: Tavily___TavilySearchPost
日本時間2025年12月29日時点におけるヴィクター・ウェンバンヤマ選手の今シーズン(2025-2026シーズン)の個人成績に関する情報として、以下が判明しています:

- **平均得点**: 26.2ポイント
- **平均リバウンド数**: 12.9リバウンド
- **平均アシスト数**: 4.0アシスト

また、彼は怪我の影響で一時的に欠場していたものの、復帰後も高いパフォーマンスを維持しています。さらに、ブロック数は1試合平均3.6と、ディフェンスでも際立った活躍を見せているとの報告があります。

You: 最後に、2025年のロサンゼルス・ドジャースのシーズン成績(勝敗)を教えてください。

Tool #7: Tavily___TavilySearchPost

Tool #8: Tavily___TavilySearchExtract
2025年のロサンゼルス・ドジャースのシーズン成績(勝敗)に関する情報を取得することができませんでした。最新の成績については、MLB公式サイトやスポーツニュースをご確認ください。

You: exit
チャットを終了します。
LAPTOP:~/ai-agent/azure-openai-agent$ 

オクラホマシティ・サンダーの成績について聞いた時は、ツールの使用が拒否されました。Tool Execution Denied: Tool call not allowed due to policy enforcement [Policy evaluation denied due to policy_9a7go-kqobls3p5x] とメッセージがあり、ポリシーが効いていることがハッキリと分かりますね。

それ以外の質問は、すべてツールを使用できているようです。ただ、ツールを使用しても情報が正しく取得できていなかったり、間違った回答をしてきていますね。。(今回はあくまでPolicyの検証がしたかっただけなのでこれ以上深堀りしませんが)

また、2つ目の質問のシェイ・ギルジャス・アレクサンダー選手はオクラホマシティ・サンダーの看板選手ですが、質問文に「オクラホマシティ・サンダー」という文言がないため、Policyには引っかからなかったようです。Policyは文脈から柔軟に判断するものではなく、あくまでルールに基づいた厳格な制御をするためのものである、ということが見て取れます。これについては、前述の公式ドキュメントにもハッキリとコンセプトが書かれています。

AI agents can dynamically adapt to solve complex problems - from processing customer inquiries to automating workflows across multiple tools and systems. However, this flexibility introduces new security challenges, as agents may inadvertently misinterpret business rules, or act outside their intended authority.

With Amazon Bedrock AgentCore Policy, developers can create policy engines, create and store deterministic policies in them and associate policy engines with gateways.

(日本語訳) AI エージェントは、顧客からの問い合わせの処理から複数のツールやシステムにまたがるワークフローの自動化まで、複雑な問題を解決するために動的に適応できます。しかし、この柔軟性は、エージェントがビジネスルールを誤って解釈したり、意図された権限を超えて動作したりする可能性があるため、新たなセキュリティ上の課題をもたらします。

Amazon Bedrock AgentCore ポリシーを使用すると、開発者はポリシーエンジンを作成し、そこに確定的なポリシーを作成して保存し、ポリシーエンジンをゲートウェイに関連付けることができます。


動作確認は以上です。

(補足)ポリシー作成時の注意点

ポリシー作成時、ハマった点が2つありましたので、補足として記載します。

注意点1:許可ルールが無いと全て拒否される

AgentCoreのポリシーも、IAMポリシーと同様、「暗黙のDeny」が存在します。そのため、拒否ルールだけを設定すると、結果的に全てのアクセスが拒否されてしまいます。 暗黙のDenyについては、以下公式ドキュメントにも記載されています。(表現は違いますが)

docs.aws.amazon.com

Default deny

All actions are denied by default. If no policies match a request, Cedar returns DENY. You must explicitly write permit policies to allow actions.
(すべてのアクションはデフォルトで拒否されます。リクエストに一致するポリシーがない場合、CedarはDENYを返します。アクションを許可するには、明示的に許可ポリシーを記述する必要があります。)


そのため、許可ルールは必ず含めるようにしましょう。

注意点2:強すぎるルールは作成できない(拒否・許可ともに)

2025年12月30日時点では、principalやactionを限定しない、強すぎるルールは作成できないようです。

最初、ポリシー作成画面で以下のプロンプトを入力しました。

NBAチーム:オクラホマシティ・サンダーに関連する問い合わせは拒否してください。
それ以外の問い合わせは全て許可してください。

結果、出来上がったプロンプトが以下です。

■許可ルール

permit(
  principal,
  action,
  resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx"
);

■拒否ルール

forbid(
  principal,
  action,
  resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx"
) when {
  ((context has input) && ((context.input) has query)) && (((context.input).query) like "*オクラホマシティ・サンダー*")
};

プロンプトとしてはほとんど同じなのですが、こちらではactionが限定されていません。


これでポリシーを作成すると、ステータスが「作成に失敗しました」となります。

上部には「Successfully created 2 policies」と表示されるが、下部では「作成に失敗しました」と表示される。どっちなんだい?


ステータスをクリックすると、エラーの詳細が表示されます。


エラーの具体的な内容はこちらです。

■許可ルールのエラーメッセージ

Overly Permissive: Policy Engine will allow every request for the specified principal (AgentCore::OAuthUser), action (Any Future Tools) and resource (gateway/*) combination if the policy is added or updated

Overly Permissive: Policy Engine will allow every request for the specified principal (AgentCore::OAuthUser), action (Tavily___TavilySearchExtract) and resource (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) combination if the policy is added or updated

Overly Permissive: Policy Engine will allow every request for the specified principal (AgentCore::OAuthUser), action (Tavily___TavilySearchPost) and resource (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) combination if the policy is added or updated

(日本語訳)
許可が広すぎます(過剰な権限): このポリシーが追加または更新されると、ポリシーエンジンは、指定されたプリンシパル (AgentCore::OAuthUser)、アクション (Any Future Tools)、およびリソース (gateway/* ) の組み合わせに対するすべてのリクエストを許可します。

許可が広すぎます(過剰な権限): このポリシーが追加または更新されると、ポリシーエンジンは、指定されたプリンシパル (AgentCore::OAuthUser)、アクション (Tavily___TavilySearchExtract)、およびリソース (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) の組み合わせに対するすべてのリクエストを許可します。

許可が広すぎます(過剰な権限): このポリシーが追加または更新されると、ポリシーエンジンは、指定されたプリンシパル (AgentCore::OAuthUser)、アクション (Tavily___TavilySearchPost)、およびリソース (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) の組み合わせに対するすべてのリクエストを許可します。

■拒否ルールのエラーメッセージ

Overly Restrictive: Policy Engine will deny every request for the specified principal (AgentCore::OAuthUser), action (Any Future Tools) and resource (gateway/*) combination if the policy is added or updated

Overly Restrictive: Policy Engine will deny every request for the specified principal (AgentCore::OAuthUser), action (Tavily___TavilySearchExtract) and resource (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) combination if the policy is added or updated

Overly Restrictive: Policy Engine will deny every request for the specified principal (AgentCore::OAuthUser), action (Tavily___TavilySearchPost) and resource (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) combination if the policy is added or updated


(日本語訳)
制限が厳しすぎます: このポリシーが追加または更新されると、ポリシーエンジンは、指定されたプリンシパル (AgentCore::OAuthUser)、アクション (Any Future Tools)、およびリソース (gateway/* ) の組み合わせに対するすべてのリクエストを拒否します。

制限が厳しすぎます: このポリシーが追加または更新されると、ポリシーエンジンは、指定されたプリンシパル (AgentCore::OAuthUser)、アクション (Tavily___TavilySearchExtract)、およびリソース (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) の組み合わせに対するすべてのリクエストを拒否します。

制限が厳しすぎます: このポリシーが追加または更新されると、ポリシーエンジンは、指定されたプリンシパル (AgentCore::OAuthUser)、アクション (Tavily___TavilySearchPost)、およびリソース (arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:gateway/tavily-gateway-xxxxxxxxxx) の組み合わせに対するすべてのリクエストを拒否します。


設定する前は「自然言語でポリシー設定できるから簡単で良いな」と思っていたのですが、いざやってみると、なかなかコントロールが難しいですねこれは。。

おわりに(実際に使ってみた感想)

以上、Policyの動作検証でした。この機能は、下記のサンプルのような、ルールベースで明確に指定できるものに使うべきだと感じました。

docs.aws.amazon.com

「〇〇に関連する話題は避ける」といった曖昧さの残る制御は、システムプロンプトに含める等、別の対応をした方が良い気がしました。
セキュリティとガバナンスを維持するために、エージェントの動きを適切に制御するには、Bedrock Guardrails 等も含めた、複数のサービス・機能を組み合わせる必要がありそうです。これから使い分けや組み合わせを勉強していきたいと思います。

今回のブログは以上です。何か少しでも参考になることがあれば幸いです。