区分两类 AccessDeniedException:IAM 授权失败(含 ARN)与模型账号未agreement(无 ARN),快速定位权限问题。
IAM 授权失败会指明主体(principal)和资源:
botocore.errorfactory.AccessDeniedException: An error occurred
(AccessDeniedException) when calling the InvokeModel operation:
User: arn:aws:sts::111122223333:assumed-role/my-fn-role/my-fn is not
authorized to perform: bedrock:InvokeModel on resource:
arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0
模型协议(model-agreement)失败则不会包含这些信息。错误中没有 principal ARN,因为请求已经通过了授权校验,只是因为另一个原因被拒绝了——该账户在该 Region 没有该模型的协议。它的措辞针对的是账户和模型,而非用户和操作。这个结构上的差异就是最快的诊断手段:如果消息中包含"is not authorized to perform"后面跟着一个 ARN,那就是 IAM 问题;如果不包含,就别再改策略了。
还有一种时序问题会短暂地产生第二种错误形式。亚马逊文档说明,账户中首次调用第三方模型会在后台启动一个订阅,这个过程最多需要十五分钟才能完成,而在必要权限就位后,订阅完全生效还需要最多两分钟——在此期间调用会持续返回 AccessDeniedException。如果你在什么都没动的情况下,几分钟后拒绝自动消失了,那就是这种情况。
当消息中确实指明了主体时,看主体,不要看策略。错误中的 ARN 是实际对请求签名的实体,在 Lambda 上它是一个假设角色 ARN,形式为 arn:aws:sts::account:assumed-role/<role-name>/<function-name>。这里有三件事容易出错,按频率排序:
错误中的角色不是你编辑的角色。IaC 栈的反复部署可能导致函数指向旧角色。将异常中的名称与 aws lambda get-function-configuration --function-name X 的结果对比。
资源 ARN 不匹配。这是 inference-profile 的情况。策略中写的是基础模型,代码传入的却是 profile ARN 作为 modelId,二者是不同的 IAM 资源——你需要为每个单独写一条语句。错误中打印的 ARN 才是需要添加的那个。
操作对调用来说不正确。只授予 bedrock:InvokeModel 的策略会拒绝 bedrock:InvokeModelWithResponseStream,所以问题出现在有人开启流式传输而其他什么都没变的那一天。bedrock:InvokeModel* 覆盖全部四种调用操作。
如果身份策略明确授予了操作但调用仍然失败,说明某处有一个显式 Deny 在起作用。在 AWS 的评估顺序中,任何显式 Deny 都优先于所有 Allow,有三个地方可能藏着它:
服务控制策略(Service Control Policy)。组织通常默认禁用新服务,亚马逊自己的模型访问指南推荐的就是这个模式——在组织或账户层面用 Deny 限制 bedrock:InvokeModel。你在账户内看不到它,也无法从账户侧修复它。
执行角色上的权限边界(Permissions Boundary),它限制了角色能做的事,不管角色上附加了什么策略。
VPC 端点策略。这是真正位于请求路径上的资源策略。如果函数运行在 VPC 中并通过接口端点访问 Bedrock,端点自己的策略也会被评估,如果一个自定义策略列出了 bedrock:InvokeModel 但没有流式操作,就会对看起来正确的角色产生同样的拒绝。见端点策略一节。
aws iam simulate-principal-policy 可以区分前两种:如果是 SCP 或边界导致的,它返回 explicitDeny 而不是 implicitDeny。但它不会评估 VPC 端点策略,所以如果模拟器说允许但运行时说拒绝,端点就是剩余的怀疑对象。
模型访问是按账户和按 Region 分开的。Lambda 函数从执行环境中的 AWS_REGION 继承其 Region,SDK 会使用该值除非你显式传入 region_name,所以一个部署到 eu-west-1 的函数会在 eu-west-1 调用,而你的控制台——一切正常的地方——在 us-east-1。
import boto3
# 固定它。不要从函数运行环境继承。
client = boto3.client("bedrock-runtime", region_name="us-east-1")
response = client.converse(
modelId="anthropic.claude-3-haiku-20240307-v1:0",
messages=[{"role": "user", "content": [{"text": "ping"}]}],
)
要验证协议而不是猜测,亚马逊暴露了 GetFoundationModelAvailability。它的响应包含 agreementAvailability.status,当访问存在时为 AVAILABLE,不存在时为 NOT_AVAILABLE,同时还有 regionAvailability 和 entitlementAvailability:
aws bedrock get-foundation-model-availability \
--model-id anthropic.claude-3-haiku-20240307-v1:0 \
--region us-east-1
还有一个针对 Anthropic 模型的按账户门槛:亚马逊文档说明需要通过控制台或 PutUseCaseForModelAccess API 提交一次性用例表单,每个账户或组织的治理账户需要提交一次。如果你的自动化流程创建新账户,这个表单就是它会忘记的那一步。
读异常。存在 principal ARN 就是 IAM;不存在就是协议或 Region 问题。
确认错误中的角色是你认为你编辑过的角色,并且错误中的资源 ARN 完整地出现在某条策略语句中。
对该操作和资源运行 simulate-principal-policy。如果是 explicitDeny 就在账户层面结束调查,转到组织层面。
在函数实际使用的 Region 运行 get-foundation-model-availability——记录 AWS_REGION 以确认是哪个 Region。
如果函数在 VPC 中,最后再看端点策略,因为这些检查项中只有它模拟器看不到。
一旦眼前的拒绝问题解决,有三个改动可以阻止同样的问题在另一个账户或 Region 再次出现。
不要重试。AccessDeniedException 是 403,AWS SDK 会正确地将其视为不可重试,所以如果你看到每个请求三次这个错误,说明你的代码在捕获一个宽泛的异常类然后循环。这会把一个即时、清晰的失败变成慢速失败,而且在 Marketplace 订阅窗口期间的首次调用时,还会疯狂请求一个本来马上就要开始工作的端点。捕获 ClientError,读取 err.response["Error"]["Code"],除了限流或超时之外的一切都立即失败。
日志要记全消息,一次。大量应用代码的默认处理器会截断地记录 str(e),或者用一个友好的字符串替换它,而 principal ARN 和资源 ARN——这里唯一重要的两个信息——恰恰就是被丢掉的部分。在 error 级别记录完整异常文本,同时记录 AWS_REGION 的值和调用使用的 modelId。这三个字段回答了本文前四节的所有问题,无需任何人复现任何东西。
把检查移到部署时。这个异常在全新环境中如此常见的原因是,在真实流量到达之前没有任何东西验证账户的 Bedrock 姿态。GetFoundationModelAvailability 很便宜,不需要 token,能给出明确的答案,所以在部署后对栈使用的每个模型 id——在栈部署到的 Region——调用一次冒烟测试,就能把生产环境的 403 变成一个失败的流水线阶段。把它与针对已部署执行角色、针对同一模型 ARN 的单次 simulate-principal-policy 调用配对,你就同时覆盖了两种原因,在任何用户看到任何一个之前。这与刻意练习提供者失败路径的逻辑相同:那些只在生产环境出现的场景才是值得在别处强制执行的。
Writing an IAM Role Scoped to a Single Bedrock Model
Setting Up a VPC Endpoint for Amazon Bedrock
Fixing ThrottlingException on Amazon Bedrock