今北産業
- Bedrock Mantleを呼ぶ時の認証方法
- 実はAPIキーレスでも呼べる
- 実装時のあれこれ
Bedrock Mantleとは
下記の記事を参照ください。本記事でBedrock Mantleについての基本的な説明は行いません。
Mantle エンドポイントの呼び出し方
Mantle へのリクエストは以下のようにBedrockの短期/長期APIキーを事前に発行して行うとドキュメントにはあります。
ええ、わざわざキーを発行すんのかよ...
client = OpenAI(
api_key="<your Amazon Bedrock API key>",
base_url="https://bedrock-mantle.us-west-2.api.aws/v1",
)
ですがこっちにはMantleの認証は2択だと書いてあります。
Authentication – Depends on the endpoint:
bedrock-mantle– Use a Amazon Bedrock API key (see API keys) or AWS SigV4 credentials.
AWS の機械学習ブログにも書かれています。
For the
bedrock-mantleendpoint, you need an Amazon Bedrock API key or AWS credentials configured for SigV4.
つまりAPIキーをいちいち発行して煩わしい管理をしなくてもいいということだな?
Mantle の認証は2通りある
ドキュメントのMantleの認証方式を整理すると以下のようになります。サービス側は2つの認証方式を受け付けます。
| 方式 | 使うもの | 必要な IAM アクション |
|---|---|---|
| SigV4 | AWS 認証情報でリクエストを署名 |
bedrock-mantle:CreateInference / Get* / List*
|
| ベアラートークン | Bedrock API キー | 上記 + bedrock-mantle:CallWithBearerToken
|
注目したいのは、モデル別のアクションが存在しないことです。bedrock-mantle:CreateInference ひとつで全モデルが対象になります。IAM リソースも arn:aws:bedrock-mantle:...:project/* であって、モデルではなくプロジェクト単位です。
つまり「Claude を呼ぶときと GPT-OSS を呼ぶときで認証処理を変える」必要はありません。モデルを差し替えても認証コードは1行も変わりません
素の SigV4 で叩いてみる
まず一番シンプルな形を試します。botocore の SigV4Auth で署名して、requests で投げるだけ。サービス名は bedrock-mantle です。
import json
import boto3
import requests
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
REGION = "ap-northeast-1"
SERVICE = "bedrock-mantle"
URL = f"https://{SERVICE}.{REGION}.api.aws/v1/chat/completions"
payload = {
"model": "google.gemma-3-4b-it",
"messages": [{"role": "user", "content": "Reply with exactly: OK"}],
"max_tokens": 5,
}
body = json.dumps(payload)
creds = boto3.Session().get_credentials().get_frozen_credentials()
req = AWSRequest(method="POST", url=URL, data=body,
headers={"Content-Type": "application/json"})
SigV4Auth(creds, SERVICE, REGION).add_auth(req)
resp = requests.post(URL, data=body, headers=dict(req.headers), timeout=60)
print(resp.status_code, resp.json()["choices"][0]["message"]["content"])
これで 200 OK が返ってきます。Authorization: Bearer も x-api-key も送っていません。IAM の権限も CallWithBearerToken なしで通りました。先ほどの MiniMax のブログが「純 SigV4 だけなら2つ目のステートメントは省略できる」と書いているとおりです。
OpenAI SDK を使うと話が変わる
Mantle は OpenAI 互換エンドポイントなので OpenAI SDK をそのまま使いたくなりますが、OpenAI SDK は SigV4 署名ができません。AWS のブログにも書かれています。
When using the OpenAI SDK, you need an Amazon Bedrock API key.
えー、じゃあ結局キーが要るのかよ。となるんですが、そうではありません。このaws-bedrock-token-generator を使うと、既存の AWS 認証情報から短期のベアラートークンをその場で発行できます。
from aws_bedrock_token_generator import provide_token
from openai import OpenAI
client = OpenAI(
api_key=provide_token(region="ap-northeast-1"),
base_url="https://bedrock-mantle.ap-northeast-1.api.aws/v1",
)
このトークンは最大12時間で自動失効し、発行元の IAM ロールの権限をそのまま継承します。SSO プロファイルでも実行ロールでも、標準のクレデンシャルチェーンから拾ってくれる。保存すべき秘密はどこにも生まれません。
認証経路について
え?OpenAI SDK は SigV4 署名できないんじゃないの?
AWS も API キーが要ると言っている。なのにキーを1本も作らずに動くのはなぜか。
でもprovide_token()の中でいい感じの処理をしてapi_keyとして代入してるんですよね。
やっていることとしては、SigV4を使ったダミーのリクエストを送り短期間のトークンを生成後、それを利用してAPIキーっぽく利用してるだけですね。
ただ、ここで重要なのはOpenAI SDK側までAWSの認証情報は伝わってないってことです。
最終的に到達するmantleエンドポイントで複合して初めてAWSの認証情報を突き合わせる感じです。
トークンの中身を覗いてみる
実際にトークン生成をしている処理はこのようになっています。
request = AWSRequest(
method="POST",
url="https://bedrock.amazonaws.com/",
params={"Action": "CallWithBearerToken"},
)
auth = SigV4QueryAuth(credentials, "bedrock", region, expires=43200)
auth.add_auth(request)
presigned_url = request.url.replace("https://", "") + "&Version=1"
encoded_token = base64.b64encode(presigned_url.encode()).decode()
return f"bedrock-api-key-{encoded_token}"
ダミーのリクエストを presign して、出来上がった URL を丸ごと base64 して、頭に bedrock-api-key- を付けてるだけでした。
実際に発行したトークンを base64 から戻すとこうなります。署名とクレデンシャルの値は伏せてます。
bedrock.amazonaws.com/?Action=CallWithBearerToken
&X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=<redacted>
&X-Amz-Date=<redacted>
&X-Amz-Expires=43200
&X-Amz-SignedHeaders=host
&X-Amz-Signature=<redacted>
&Version=1
ここまでをまとめると、
-
X-Amz-Expires=43200が「最大12時間で失効」の正体。presign の有効期限そのものです -
Action=CallWithBearerTokenが埋め込まれてるから、IAM にbedrock-mantle:CallWithBearerTokenが要るわけですね - 署名しているサービス名が
bedrock-mantleではなくbedrockなので、このトークンは Bedrock 共通で Mantle 専用ではないようです
そして一番スッキリしたのがここです。「APIキー」と呼ばれてはいますが、新しく作られた秘密ではなく、手持ちの AWS 認証情報から導出した署名そのものでした。だから保存する必要がないわけです。
Strands Agents
Strands Agents を使う場合、この配線は既にライブラリ側で済んでいます。bedrock_mantle_config に region を渡すだけ。
from strands import Agent
from strands.models.openai import OpenAIModel
model = OpenAIModel(
model_id="openai.gpt-oss-120b",
bedrock_mantle_config={"region": "ap-northeast-1"}, # api_key は渡さない
)
agent = Agent(model=model, tools=[get_weather])
受け付けるキーは region / boto_session / credentials_provider / expiry の4つ。全部省略可能で、何も渡さなければ標準のクレデンシャルチェーンを使います。
AgentCore Runtime に載せてもいける
AgentCore Runtimeも問題なくいけます。コンテナの中では実行ロールの一時認証情報が取れるので、環境変数にもシークレットストアにも何も置かずに Mantleを呼べます。
CDK だとこうなります。
const runtime = new agentcore.Runtime(this, 'MantleAgentRuntime', {
runtimeName: 'mantle_agent',
agentRuntimeArtifact: artifact,
environmentVariables: {
MANTLE_REGION: this.region,
MANTLE_MODEL_ID: 'openai.gpt-oss-120b',
},
tracingEnabled: true,
});
runtime.role.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonBedrockMantleInferenceAccess'),
);
AmazonBedrockMantleInferenceAccess は SigV4 経路とベアラートークン経路の両方をカバーしてくれるので、とりあえずこれを付けておけば動きます。中身はこんな構成でした。
{
"Sid": "BedrockMantleInference",
"Action": ["bedrock-mantle:Get*", "bedrock-mantle:List*", "bedrock-mantle:CreateInference"],
"Resource": "arn:aws:bedrock-mantle:*:*:project/*"
}
デプロイして呼んでみると、ちゃんと動きました。
$ python scripts/invoke_runtime.py "いま何時ですか?"
[tool: get_current_time]
現在の時刻は 2026年8月8日 21時24分37秒(JST)です。
長期 API キーはゼロ。.env にも Secrets Manager にも何も置いていません。
APIキー方式は悪なのか?
キーを発行しないで済むならそっちの方が安全だしいいじゃん!
となるかもしれません。ここまでなら。
ですが、上で書いたように短期APIキーは内部的にはSigV4認証です。
じゃあ長期APIキーをわざわざ発行するメリットは...
ここは場合によるとしか言えません...笑
例えば、呼び出し元に AWS 認証情報を持たせられない、取引先のシステム、社外の人のノート PC。短期トークンは発行するために AWS 認証情報が要るので、そもそも使えません。長期キーを1本渡すほうが、IAM ユーザーを作って認証情報一式を配るより管理が軽く済みます。
キーを管理するのもコストになるので、どちらの方式で呼び出すか、よく考えた方がいいですね。
でもドキュメントやサンプルはAPIキー方式だよね?
それはMantleのコンセプトとして、OpenAI互換だから〜ということも理由にあると思います。「既存コードの base_url と api_key を差し替えるだけ」という体験をすっきりとした形で見せたいんだと思います笑
さいご
公式の実行サンプルがAPIキー前提のものばかりなので、読み返すまでキーなしでいけると気づきませんでした。
参考
