Setting the file. One moment.
IAM Policy · LLM To Bedrock · aws/agent-toolkit-for-aws · Skills Docs
ContentsBack to the top of the page 81
Creating Amazon Aurora Db Cluster With Instances
104
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Creating API Gateway Stage
Next
Script Image Input
scripts/ iam_policy.py
Python · 132 lines · 5 KB
)
\.
"
)
12
13
14 def is_inference_profile (model_id: str ) -> bool :
15 """True when the model ID uses a geo-prefix (cross-region inference profile)."""
16 return bool ( _GEO_PREFIX .match(model_id))
17
18
19 def is_mantle_model (model_id: str ) -> bool :
20 """True for OpenAI's proprietary GPT models, which are served only on the
21 bedrock-mantle endpoint. They need `bedrock-mantle:*` actions — a policy
22 granting only `bedrock:InvokeModel` against a foundation-model ARN cannot
23 authorize them, and they have no inference profile to scope to either.
24 The open-weight gpt-oss models DO use bedrock-runtime and must not match."""
25 mid = model_id.lower()
26 return mid.startswith( "openai.gpt-5" ) and "oss" not in mid
27
28
29 def mantle_project_arn (region: str , account_id: str ) -> str :
30 """ARN scope for mantle inference. Mantle authorizes at project granularity,
31 not per model, so this cannot be narrowed to specific model IDs — use a
32 service control policy to restrict the model set."""
33 return f "arn:aws:bedrock-mantle: { region } : { account_id } :project/*"
34
35
36 def foundation_model_arn (model_id: str ) -> str :
37 """ARN for a plain foundation-model ID (no geo prefix)."""
38 return f "arn:aws:bedrock:*::foundation-model/ { model_id } "
39
40
41 def inference_profile_arn (model_id: str , region: str , account_id: str ) -> str :
42 """ARN for a cross-region inference profile."""
43 return f "arn:aws:bedrock: { region } : { account_id } :inference-profile/ { model_id } "
44
45
46 def generate_policy (model_ids: list[ str ], region: str , account_id: str ) -> dict :
47 """Build a scoped IAM policy covering exactly the given model IDs.
48
49 Emits up to three statements, depending on which endpoints the targets use:
50 - `bedrock:InvokeModel*` scoped to foundation-model ARNs (plain IDs) and
51 inference-profile ARNs (geo-prefixed IDs), for bedrock-runtime targets
52 - `bedrock-mantle:CreateInference` / `Get*` / `List*` scoped to the account's
53 mantle projects, for mantle-only targets (SigV4 auth)
54 - `bedrock-mantle:CallWithBearerToken` on `*`, for Bedrock API-key auth
55
56 A statement is omitted entirely when no target needs it — notably, an
57 all-mantle run must not emit an InvokeModel statement with an empty Resource
58 list, which is an invalid policy.
59 """
60 runtime_ids = [m for m in model_ids if not is_mantle_model(m)]
61 mantle_ids = [m for m in model_ids if is_mantle_model(m)]
62
63 statements = []
64
65 resources = []
66 for mid in sorted ( set (runtime_ids)):
67 if is_inference_profile(mid):
68 resources.append(inference_profile_arn(mid, region, account_id))
69 base_id = _GEO_PREFIX .sub( "" , mid)
70 resources.append(foundation_model_arn(base_id))
71 else :
72 resources.append(foundation_model_arn(mid))
73
74 # Emit unless the ONLY reason there are no resources is that every target is
75 # mantle-only. A genuinely empty model list keeps the legacy shape (a statement
76 # with an empty Resource) so existing callers and tests see no behaviour change.
77 if resources or not mantle_ids:
78 statements.append({
79 "Sid" : "BedrockInvokeModelScoped" ,
80 "Effect" : "Allow" ,
81 "Action" : [
82 "bedrock:InvokeModel" ,
83 "bedrock:InvokeModelWithResponseStream" ,
84 ],
85 "Resource" : sorted ( set (resources)),
86 })
87
88 if mantle_ids:
89 statements.append({
90 "Sid" : "BedrockMantleInference" ,
91 "Effect" : "Allow" ,
92 "Action" : [
93 "bedrock-mantle:CreateInference" ,
94 "bedrock-mantle:Get*" ,
95 "bedrock-mantle:List*" ,
96 ],
97 "Resource" : mantle_project_arn(region, account_id),
98 })
99 # CallWithBearerToken must be scoped to "*" — AWS does not support
100 # narrowing it. Required for Bedrock API-key (bearer token) auth, which is
101 # how the rewriter's generated client authenticates. Omit only if the app
102 # uses SigV4 exclusively.
103 statements.append({
104 "Sid" : "BedrockMantleCallWithBearerToken" ,
105 "Effect" : "Allow" ,
106 "Action" : [ "bedrock-mantle:CallWithBearerToken" ],
107 "Resource" : "*" ,
108 })
109
110 return { "Version" : "2012-10-17" , "Statement" : statements}
111
112
113 if __name__ == "__main__" :
114 import argparse
115
116 parser = argparse.ArgumentParser( description = "Generate scoped Bedrock IAM policy" )
117 parser.add_argument( "--models" , required = True , help = "Comma-separated model IDs" )
118 parser.add_argument( "--region" , required = True , help = "AWS region" )
119 parser.add_argument( "--account-id" , required = True , help = "AWS account ID" )
120 parser.add_argument( "--output" , help = "Output file (default: stdout)" )
121 args = parser.parse_args()
122
123 model_ids = [m.strip() for m in args.models.split( "," ) if m.strip()]
124 policy = generate_policy(model_ids, args.region, args.account_id)
125
126 output = json.dumps(policy, indent = 2 ) + " \n "
127 if args.output:
128 with open (args.output, "w" ) as f:
129 f.write(output)
130 print ( f "Policy written to { args.output } " , file = sys.stderr)
131 else :
132 print (output)