はじめに
Amazon Bedrock Knowledge Base と OpenSearch Serverless を CDK (TypeScript) で構築したとき、正直かなり詰まりました。
公式ドキュメントを読んでも「なんかエラーが出る」「IAM を直したはずなのに動かない」という状態が続きます。原因の多くは CDK 特有の落とし穴や、OpenSearch Serverless 固有の仕様で、コンソール操作ベースの記事では拾えない情報です。
この記事では、実装を通じて実際に踏んだ7つのハマりポイントを、解決コード付きでまとめます。
構築全体の解説は以下記事を参照してください。
①: JSON.stringify は CFn トークンを壊す
DataAccessPolicy の policy に IAM ロール ARN を埋め込む際、JSON.stringify を使うと CDK の CFn トークンが解決されません。
// NG: JSON.stringify が CDK トークンを文字列化してしまう
policy: JSON.stringify([{
Principal: [props.knowledgeBaseRole.roleArn],
// → "${Token[TOKEN.123]}" という文字列になる
}])
CloudFormation に渡る JSON がこうなります:
"Principal": ["${Token[TOKEN.123]}"]
OpenSearch Serverless はこのトークン文字列を ARN として認識できないため 403 になります。
CDK 公式が推奨する方法は stack.toJsonString() です。トークンを含むオブジェクトを JSON 文字列化する際に使います。
// OK: stack.toJsonString() はトークンを正しく処理する
const dataAccessPolicy = Stack.of(this).toJsonString([{
Principal: [props.knowledgeBaseRole.roleArn, indexCreatorRole.roleArn],
}]);
cdk.Fn.sub でも回避できます(プレースホルダーを明示的に置換する方法):
// OK: CFn デプロイ時に ARN が解決される
const dataAccessPolicy = cdk.Fn.sub(
JSON.stringify([{
Principal: ['${KnowledgeBaseRoleArn}', '${IndexCreatorRoleArn}'],
}]),
{
KnowledgeBaseRoleArn: props.knowledgeBaseRole.roleArn,
IndexCreatorRoleArn: indexCreatorRole.roleArn,
}
);
②: OpenSearch Serverless のアクセス制御は2層構造
OpenSearch Serverless のアクセス制御は IAM ポリシーと DataAccessPolicy の 両方が必要 です。片方だけでは 403 になります。
Being granted permissions within a data access policy is not sufficient to access data in your OpenSearch Serverless collection. An associated principal must also be granted access to the IAM permissions
aoss:APIAccessAllandaoss:DashboardsAccessAll.
| レイヤー | 役割 | 設定場所 |
|---|---|---|
| IAM ポリシー | コントロールプレーン API へのアクセス制御 | IAM ロールのインラインポリシー |
| DataAccessPolicy | インデックス操作(データプレーン)へのアクセス制御 | CfnAccessPolicy |
なお aoss:APIAccessAll のリソースにはコレクション ARN を指定できます(* でも可)。
参照: Identity and Access Management for Amazon OpenSearch Serverless
③: Custom Resource が DataAccessPolicy より先に実行される
CloudFormation はリソースを並列で作成します。IndexCreator(Custom Resource)と DataAccessPolicy の間に依存関係がないと、DataAccessPolicy の作成完了前に Lambda が実行されて 403 になります。
const indexCreator = new cdk.CustomResource(this, 'IndexCreator', {
serviceToken: provider.serviceToken,
});
// DataAccessPolicy 作成完了後に実行されるよう依存関係を明示する
indexCreator.node.addDependency(cfnDataAccessPolicy);
④: botocore.SigV4Auth + urllib は IAM ロールで 403 になる
Custom Resource Lambda でインデックスを作成する際、botocore.SigV4Auth + urllib の組み合わせでは IAM ロールの AssumedRole セッションが 403 で弾かれます。
切り分けの結果:
| 実行者 | ライブラリ | 結果 |
|---|---|---|
| IAM ユーザー | requests-aws4auth | ✅ 200 |
| IAM ロール(AssumedRole) | botocore.SigV4Auth + urllib | ❌ 403 |
| IAM ロール(AssumedRole) | requests-aws4auth | ✅ 200 |
requests + requests-aws4auth を Lambda Layer としてバンドルして解決しました。
# NG
from botocore.auth import SigV4Auth
# → IAM ロールの AssumedRole セッションで 403
# OK
from requests_aws4auth import AWS4Auth
creds = session.get_credentials()
auth = AWS4Auth(creds.access_key, creds.secret_key, region, 'aoss',
session_token=creds.token)
resp = requests.put(url, json=body, auth=auth)
CDK 側では Lambda Layer を追加します:
const requestsLayer = new lambda.LayerVersion(this, 'RequestsLayer', {
code: lambda.Code.fromAsset(path.join(__dirname, '../lambda-layer')),
compatibleRuntimes: [lambda.Runtime.PYTHON_3_12],
});
Layer のセットアップ(初回のみ):
mkdir -p cdk/lambda-layer/python
pip install requests requests-aws4auth -t cdk/lambda-layer/python
⑤: ロール名を固定しないとスタック再作成で ARN が変わる
CDK はロール名を指定しない場合、StackName-LogicalId-RandomSuffix 形式で自動生成します。スタックを削除して再作成するたびにサフィックスが変わるため、DataAccessPolicy に登録済みの ARN と実際のロール ARN が一致しなくなります。
const indexCreatorRole = new iam.Role(this, 'IndexCreatorRole', {
roleName: 'rag-index-creator-role', // 固定する
// ...
});
⑥: bedrock:RetrieveAndGenerate は KB ARN にスコープできない
bedrock:RetrieveAndGenerate は resource: '*' 固定 にする必要があります。
// RetrieveAndGenerate は resource: '*' 固定(KB ARN にスコープ不可)
new iam.PolicyStatement({
actions: ['bedrock:RetrieveAndGenerate'],
resources: ['*'],
});
// Retrieve は KB ARN にスコープ可能
new iam.PolicyStatement({
actions: ['bedrock:Retrieve'],
resources: [knowledgeBaseArn],
});
// InvokeModel はモデル ARN にスコープ可能
new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: ['arn:aws:bedrock:ap-northeast-1::foundation-model/amazon.nova-lite-v1:0'],
});
bedrock:RetrieveAndGenerate だけリソース指定のルールが違います。ここを knowledgeBaseArn にしてしまうと AccessDenied が発生します。
Service Authorization Reference の Actions テーブルで RetrieveAndGenerate の Resource types 列が空欄になっており、リソースレベルの権限をサポートしていないことが確認できます。
参照: Actions defined by Amazon Bedrock
⑦: クロスリージョン推論プロファイルは使わない
us.amazon.nova-micro-v1:0 のような us. プレフィックスのモデル ID を使うと、2つの問題が起きます。
- データが別リージョンに飛ぶ(データレジデンシーの問題)
- IAM ポリシーが3ステートメント必要になり複雑(推論プロファイル ARN・リージョン FM ARN・グローバル FM ARN の3つを個別に Allow する必要がある)
特に2点目は気づきにくいです。クロスリージョン推論プロファイルには arn:aws:bedrock:REGION:ACCOUNT:inference-profile/global.MODEL-NAME という ARN が存在しますが、それに加えてリージョン FM ARN とグローバル FM ARN の計3つの IAM ステートメントが必要です。
参照: IAM policy requirements for global cross-Region inference
ap-northeast-1 でシングルリージョン固定できるモデルを使うのが正解です。学習・検証用途であれば Nova Lite がおすすめです。
// parameter.ts で一元管理
export const parameter = {
models: {
embedding: "arn:aws:bedrock:ap-northeast-1::foundation-model/amazon.titan-embed-text-v2:0",
generation: "arn:aws:bedrock:ap-northeast-1::foundation-model/amazon.nova-lite-v1:0",
},
};
まとめ
CDK × Bedrock KB + OpenSearch Serverless 実装で踏みやすい落とし穴は次の7つです。
-
JSON.stringifyに CDK トークンを渡さない。動的な値を JSON 文字列に埋め込む場合はstack.toJsonString()またはcdk.Fn.subを使う - OpenSearch Serverless のアクセス制御は IAM ポリシーと DataAccessPolicy の 2層が両方必要
- Custom Resource の実行順序は
addDependencyで明示的に制御する - OpenSearch Serverless への署名は
requests-aws4authを使う(botocore.SigV4Auth+urllibは IAM ロールで動作しない) - DataAccessPolicy の Principal に使うロールは
roleNameを固定する -
bedrock:RetrieveAndGenerateの IAM リソースは'*'固定 - クロスリージョン推論プロファイルは使わず、ap-northeast-1 シングルリージョンのモデルを使う
どれも「ドキュメントに書いてあるけど見落としやすい」類のミスで、再現性のあるパターンです。
同じところで詰まっている方の参考になれば嬉しいです。
参考文献
- Data access control for Amazon OpenSearch Serverless
- Identity and Access Management for Amazon OpenSearch Serverless
- Tokens and the AWS CDK
- Actions, resources, and condition keys for Amazon Bedrock
- IAM policy requirements for global cross-Region inference
- Set up permissions for a user or role to create and manage knowledge bases