はじめに
Amazon Bedrock AgentCore考察記事第4弾。
前回の記事では、Amazon Bedrock AgentCore Gatewayの全体像に関して、公式のDeveloper Guideをなぞる形で構築と考察を行った。
公式のDeveloper Guideには、Auth0を用いたインバウンド認証の実施方法が書かれているものの、M2Mアプリケーションであるため、ユーザ認証をしなくてもアプリを実行したら認証を行える内容が書かれている。
実際のユースケースでは、Auth0に限らず、OktaなりAzure ADなりの認証を通したユーザのみ実行できるようにしたいだろう。
また、同じ組織の全員が実行できてしまうと具合が悪いケースがあるので、ユーザ単位のパーミッションを設定可能とし、認可も作り込んだ。
なお、記事中で使用する以下の要素は前回記事で作成したものを流用する。
要は、インバウンド認証に関する差分の身を記載するため、不明なリソースは前回記事を参照していただきたい。
- IAMロール
- Amazon Bedrock AgentCore GatewayのTerraformリソース(externalデータソース)
- Amazon Bedrock AgentCore Gatewayのアウトバウンド認証に使うリソース(externalデータソース)
Auth0
前回の記事では、インバウンドとアウトバウンドの認証で同じリソースサーバ・クライアントを使っていたが、アウトバウンド認証はM2Mでないと動作しない、かつユーザ認証はM2Mでは動作しないため、リソースを分割する。
Resource Server
前回とあまり変更するところはないが、リソースを分ける都合、name
, identifier
を変えておこう。
また、token_dialect
は不要なので、明示的にaccess_token
を設定しておく。
resource "auth0_resource_server" "amazon_bedrock_agentcore_gateway_ib_auth" {
name = "Amazon Bedrock AgentCore Gateway Inbound Resource Server"
identifier = "urn:${local.auth0_resource_server_id_ib}:api"
signing_alg = "RS256"
allow_offline_access = false
token_lifetime = 3600
skip_consent_for_verifiable_first_party_clients = true
enforce_policies = true
token_dialect = "access_token"
}
クライアント
ここは、Resource Server同様にname
の変更もあるが、ユーザ認証をするためにapp_type = "native"
を設定しよう。
また、grant_types
についても、client_credentials
ではなく"urn:ietf:params:oauth:grant-type:device_code
を設定しておく。
※この後出てくるユーザ認証を呼び出すためのgrant
resource "auth0_client" "amazon_bedrock_agentcore_gateway_ib_auth" {
name = "Amazon Bedrock AgentCore Gateway Inbound API"
description = "API for Amazon Bedrock AgentCore Gateway Inbound authorization"
app_type = "native"
custom_login_page_on = false
is_first_party = true
is_token_endpoint_ip_header_trusted = false
oidc_conformant = true
require_proof_of_possession = false
grant_types = [
"urn:ietf:params:oauth:grant-type:device_code",
]
jwt_configuration {
alg = "RS256"
lifetime_in_seconds = 3600
secret_encoded = false
}
refresh_token {
leeway = 0
token_lifetime = 31557600
rotation_type = "non-rotating"
expiration_type = "non-expiring"
}
}
ロール関連
さて、今回のキモになるのがロール関連のリソースだ。
invoke:mcp
という権限を持ったロールを作り、ログインしたユーザにそのロールがアタッチされていたら認証を通す、という作りにする。
リソースがたくさんあって分かりにくいが、ざっくり以下の内容だ。
- Resource Serverにスコープを追加する
- クライアントにスコープを追加する
- ロールを作成する
- ↑で作成したスコープを同じものをロールに権限として付与する
- ロールをユーザ(
example@gmail.com
で作成している前提でデータソースにしている)にアタッチする
resource "auth0_resource_server_scopes" "amazon_bedrock_agentcore_gateway_ib_auth" {
resource_server_identifier = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier
scopes {
name = "invoke:mcp"
}
}
resource "auth0_client_grant" "amazon_bedrock_agentcore_gateway_ib_auth" {
client_id = auth0_client.amazon_bedrock_agentcore_gateway_ib_auth.id
audience = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier
scopes = [
"invoke:mcp",
]
}
resource "auth0_role" "amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp" {
name = "amazon-bedrock-agentcore-gateway-ib-auth-invoke-mcp"
description = "Can invoke mcp"
}
resource "auth0_role_permissions" "amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp" {
depends_on = [auth0_resource_server_scopes.amazon_bedrock_agentcore_gateway_ib_auth]
role_id = auth0_role.amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp.id
permissions {
name = "invoke:mcp"
resource_server_identifier = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier
}
}
data "auth0_user" "sample_user" {
query = "email:example@gmail.com"
}
resource "auth0_user_roles" "assign_reader_to_user" {
user_id = data.auth0_user.sample_user.id
roles = [
auth0_role.amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp.id,
]
}
さらに、Amazon Bedrock Agentcore Gatewayのインバウンド認証では、認可情報の確認はしてくれないので、Auth0のpost-loginのアクションを追加して自前で認可を行う。
※この部分がトイルになるので、将来の機能拡張で対応してもらえると大変助かる……
post-loginのアクションは以下のように定義する。
どのクライアントに何のロールが設定されていれば良いかという認可のための情報は、ここでアクションシークレットとして保持する。
resource "auth0_action" "amazon_bedrock_agentcore_gateway_ib_auth" {
name = "Amazon Bedrock AgentCore Gateway Inbound Action"
runtime = "node18"
deploy = true
code = file("./auth0_action.js")
supported_triggers {
id = "post-login"
version = "v3"
}
secrets {
name = "CLIENT_${auth0_client.amazon_bedrock_agentcore_gateway_ib_auth.client_id}"
value = jsonencode([
auth0_role.amazon_bedrock_agentcore_gateway_ib_auth_invoke_mcp.name
])
}
}
resource "auth0_trigger_action" "amazon_bedrock_agentcore_gateway_ib_auth" {
trigger = "post-login"
action_id = auth0_action.amazon_bedrock_agentcore_gateway_ib_auth.id
}
呼び出すアクションスクリプトは以下のような感じだ。
内容が拙くて申し訳ないが……。
期待するロールが認証したユーザにアタッチされていない場合は、access_denied: missing required role for this application
のエラーとなるようにしている。
exports.onExecutePostLogin = async (event, api) => {
const clientId = event.client?.client_id || null;
if (!clientId) {
api.access.deny("access_denied: missing client_id");
return;
}
const normalize = (s) => String(s).replace(/[^A-Za-z0-9_]/g, "_");
const secretName = `CLIENT_${normalize(clientId)}`;
const raw = event.secrets?.[secretName];
if (!raw) {
return;
}
let requiredRoles = [];
try {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) requiredRoles = parsed.filter(Boolean).map(String);
} catch (e) {
console.error(`Invalid JSON in secret ${secretName}:`, e);
api.access.deny("access_denied: invalid role config");
return;
}
// Empty array allowed.
if (requiredRoles.length === 0) return;
// Check for roles
const userRoles = Array.isArray(event.authorization?.roles) ? event.authorization.roles : [];
// At least one match is allowed.
const hasIntersection = userRoles.some((r) => requiredRoles.includes(r));
if (!hasIntersection) {
console.error("Role check failed", { clientId, requiredRoles, userRoles });
api.access.deny("access_denied: missing required role for this application");
return;
}
};
Amazon Bedrock AgentCore Gateway
実はAmazon Bedrock AgentCore Gatewayのリソースはあまり修正する必要がない
audience
の向き先を、先に作ったインバウンド用のResource Serverに合わせておけば良い。
data "external" "bedrock_agentcore_gateway" {
program = ["python3", "./agentcore_gateway.py"]
query = {
aws_region = data.aws_region.current.region
gateway_name = "${local.bac_gateway_name}"
role_arn = aws_iam_role.bac_gateway.arn
discovery_url = "https://${var.auth0_domain}/.well-known/openid-configuration"
audience = auth0_resource_server.amazon_bedrock_agentcore_gateway_ib_auth.identifier
}
}
いざ、動かす!
さて、これでterraform apply
すれば準備はできているが、クライアント側にも細工が必要になる。
こんな関数を作って組み込もう。
前回の記事のスクリプトでは、払い出したトークンをBEARER_ACCESS_TOKEN
の環境変数に入れる前提にしていたので、今回もこの関数の中でBEARER_ACCESS_TOKEN
を更新している。
streamablehttp_client
を作る前に呼び出しておこう。
当然のことながら、払い出し前にはトークンのJWT検証など、必要な処理が他にもあるので、それは適宜追加していただきたい。
def get_token():
"""
Get Auth0 access token.
"""
device_code_response = requests.post(
f"https://{os.environ.get("AUTH0_DOMAIN")}/oauth/device/code",
data={
"client_id": os.environ.get("AUTH0_CLIENT_ID"),
"scope": "openid profile email offline_access invoke:gateway",
"audience": os.environ.get("AUTH0_AUDIENCE"),
},
).json()
print(
"Open URL to complete verification:",
device_code_response["verification_uri_complete"],
)
interval = device_code_response.get("interval", 5)
while True:
token_response = requests.post(
f"https://{os.environ.get("AUTH0_DOMAIN")}/oauth/token",
data={
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device_code_response["device_code"],
"client_id": os.environ.get("AUTH0_CLIENT_ID"),
},
)
if token_response.status_code == 200:
tokens = token_response.json()
access_token = str(tokens["access_token"])
print(f"Auth0 access token has issued.")
os.environ["BEARER_ACCESS_TOKEN"] = f"{access_token}"
return
err = token_response.json().get("error")
if err in ("authorization_pending", "slow_down"):
print(f"Getting token... reason: {token_response}")
time.sleep(interval + (2 if err == "slow_down" else 0))
else:
raise RuntimeError(f"Device flow failed: {err}")
これを動かすと、
$ python main.py "neruneruoについて教えてください"
Auth0 access token is not set. Refreshing...
Open URL to complete verification: https://xxxxxxxxxx.us.auth0.com/activate?user_code=XXXX-XXXX
Getting token... reason: <Response [403]>
Getting token... reason: <Response [403]>
Getting token... reason: <Response [403]>
という出力がされるので、ブラウザでURLを開こう。
「確認」を押下すると、ログイン画面が表示されるので、事前に登録しているユーザでログインしよう
ログインすると、以下の画面が出て、
コンソール上では処理が進んで、前回記事と同じような結果が得られる。
Auth0 access token has issued.
neruneruoという名前のプロフィール情報を調べたいようですね。その人物に関する情報を取得するために、プロフィール検索ツールを使用します。
Tool #1: xxx-bac-gw-mcps-example-gateway-target___getProfile
neruneruoさんのプロフィール情報が見つかりました。以下が情報です:
- 年齢: 30歳
- 部署: 開発部門 (development)
この情報によると、neruneruoさんは30歳で、会社の開発部門に所属していることがわかります。他に何か具体的に知りたい情報がありましたら、お気軽にお尋ねください。
次に、HCLでauth0_user_roles.assign_reader_to_user
を消して(要はロールを外して)apply後に再度認証をしてみよう。
すると、しっかりエラーになってくれる。
以下のように、Auth0のログでも、事前に設定したワードがdescriptionに表示されている。
これで、Amazon Bedrock AgentCoreでも外部IdPを使ったユーザ認証認可ができるようになった!
GAまでにもう少し使いやすくなることを期待する!