Skip to content

Commit c2a053c

Browse files
committed
docs: add permission boundaries, scope ARNs, close escalation paths
- Add iam-policy-boundary.json for execution role permission boundaries - Add deny statements to CFN execution role policy (ForceExecutionRoleBoundary, PreventBoundaryRemoval, PreventBoundaryPolicyTampering) - Scope CDK bootstrap role ARNs to single account (ACCOUNT_ID placeholder) - Expand permission boundaries section with step-by-step enterprise hardening guide
1 parent 2b84009 commit c2a053c

4 files changed

Lines changed: 162 additions & 28 deletions

File tree

docs/PERMISSIONS.md

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ who provision roles and policies in environments where access is tightly control
55
action-by-action reference. Developers who just need to get unblocked can skip to
66
[What developers need](#what-developers-need).
77

8-
Ready-to-use policy documents are provided at [iam-policy-user.json](./policies/iam-policy-user.json) and
9-
[iam-policy-cfn-execution.json](./policies/iam-policy-cfn-execution.json).
8+
Ready-to-use policy documents are provided at [iam-policy-user.json](./policies/iam-policy-user.json),
9+
[iam-policy-cfn-execution.json](./policies/iam-policy-cfn-execution.json), and
10+
[iam-policy-boundary.json](./policies/iam-policy-boundary.json). Replace `ACCOUNT_ID` placeholders with your AWS account
11+
number before deploying.
1012

1113
## How the CLI uses AWS
1214

@@ -170,23 +172,70 @@ safely removed:
170172
| Policy engine | `PolicyGeneration` | Remove `*PolicyEngine*` and `*Policy` actions from `BedrockAgentCoreResources` |
171173
| Online evaluations | Remove `UpdateOnlineEvaluationConfig` from `AgentCoreResourceStatus` | Remove `*OnlineEvaluationConfig*` actions from `BedrockAgentCoreResources` |
172174

173-
## Permission boundaries
175+
## Hardening with permission boundaries
174176

175-
If your organization uses
176-
[IAM permission boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html), there are
177-
two places they can come into play.
177+
The CFN execution role policy includes `iam:CreateRole` with `Resource: "*"`. Without further constraints,
178+
CloudFormation could theoretically create a role with `AdministratorAccess` and a trust policy allowing the developer to
179+
assume it. This is a well-known CDK privilege escalation pattern.
178180

179-
**On the developer's role.** The developer policy from [iam-policy-user.json](./policies/iam-policy-user.json) defines
180-
the maximum set of actions a developer needs. You can use this as both the identity policy and the permissions boundary,
181-
or create a broader boundary and use the policy for the effective permissions. The critical thing is that
182-
`sts:AssumeRole` on the CDK bootstrap roles must be allowed through the boundary, otherwise deployments will fail.
181+
[IAM permission boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) close this
182+
gap. A permission boundary caps the effective permissions of any role it is attached to, regardless of what identity
183+
policies that role carries. Even if someone attaches `AdministratorAccess` to an execution role, the boundary limits
184+
what the role can actually do.
183185

184-
**On roles that CloudFormation creates.** During `agentcore deploy`, CloudFormation creates execution roles for
185-
runtimes, gateways, memory stores, and other resources. If your organization requires that all IAM roles have a
186-
permissions boundary attached, you will need to ensure the CFN execution role has `iam:PutRolePermissionsBoundary`
187-
permission, and the CDK constructs in your project pass the boundary ARN to the created roles. Currently, AgentCore's
188-
CDK constructs create roles with inline policies scoped to the specific resource. These roles are not given permission
189-
boundaries by default.
186+
The setup has three parts: creating the boundary policy, adding deny statements to the CFN execution role so it is
187+
forced to apply the boundary, and scoping the user policy to a single account.
188+
189+
### Step 1: Create the execution role boundary
190+
191+
This policy defines the maximum permissions any AgentCore execution role (runtime, memory, gateway, etc.) can have at
192+
runtime. Create it once per account.
193+
194+
The provided [iam-policy-boundary.json](./policies/iam-policy-boundary.json) allows:
195+
196+
- `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` for model access
197+
- CloudWatch Logs scoped to `/aws/bedrock-agentcore/*` log groups
198+
- X-Ray trace submission
199+
- CloudWatch metrics scoped to the `bedrock-agentcore` namespace
200+
- `bedrock-agentcore:GetWorkloadAccessToken*` for identity federation
201+
202+
```bash
203+
aws iam create-policy \
204+
--policy-name AgentCoreExecutionRoleBoundary \
205+
--policy-document file://docs/policies/iam-policy-boundary.json
206+
```
207+
208+
### Step 2: Add deny statements to the CFN execution role
209+
210+
The provided [iam-policy-cfn-execution.json](./policies/iam-policy-cfn-execution.json) includes three deny statements
211+
that close the escalation paths. Replace `ACCOUNT_ID` with your account number before deploying.
212+
213+
| Statement | What it blocks |
214+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
215+
| `ForceExecutionRoleBoundary` | Denies `iam:CreateRole` unless the boundary is attached. Any role CloudFormation creates must carry `AgentCoreExecutionRoleBoundary`. |
216+
| `PreventBoundaryRemoval` | Denies `iam:DeleteRolePermissionsBoundary` and `iam:PutRolePermissionsBoundary`. Prevents removing or swapping the boundary after role creation. |
217+
| `PreventBoundaryPolicyTampering` | Denies `iam:CreatePolicyVersion`, `iam:DeletePolicy`, etc. on the boundary policy itself. Prevents widening the boundary to bypass it. |
218+
219+
Together these ensure that even though `iam:CreateRole` targets `Resource: "*"`, every created role is capped by the
220+
boundary, and neither the boundary nor its attachment can be tampered with.
221+
222+
### Step 3: Scope the user policy to your account
223+
224+
The provided [iam-policy-user.json](./policies/iam-policy-user.json) uses `ACCOUNT_ID` placeholders in the CDK bootstrap
225+
role ARNs. Replace these with your actual account ID before deploying:
226+
227+
```
228+
arn:aws:iam::ACCOUNT_ID:role/cdk-*-deploy-role-*
229+
```
230+
231+
This prevents the developer from assuming CDK bootstrap roles in other accounts.
232+
233+
### What about the developer's own role?
234+
235+
The developer policy from [iam-policy-user.json](./policies/iam-policy-user.json) does not include `iam:CreateRole` or
236+
any IAM write actions. Developers interact with IAM only indirectly through CloudFormation, which is constrained by the
237+
boundary. If your organization applies permission boundaries to all IAM principals, you can use the developer policy
238+
itself as the boundary, but make sure `sts:AssumeRole` on the CDK bootstrap roles is allowed through it.
190239

191240
## What developers need
192241

@@ -239,12 +288,12 @@ outside of CloudFormation.
239288

240289
Required for all deployment operations (`deploy`, `status`, `diff`).
241290

242-
| Action | Resource |
243-
| ---------------- | --------------------------------------------------- |
244-
| `sts:AssumeRole` | `arn:aws:iam::*:role/cdk-*-deploy-role-*` |
245-
| `sts:AssumeRole` | `arn:aws:iam::*:role/cdk-*-file-publishing-role-*` |
246-
| `sts:AssumeRole` | `arn:aws:iam::*:role/cdk-*-image-publishing-role-*` |
247-
| `sts:AssumeRole` | `arn:aws:iam::*:role/cdk-*-lookup-role-*` |
291+
| Action | Resource |
292+
| ---------------- | ------------------------------------------------------------ |
293+
| `sts:AssumeRole` | `arn:aws:iam::ACCOUNT_ID:role/cdk-*-deploy-role-*` |
294+
| `sts:AssumeRole` | `arn:aws:iam::ACCOUNT_ID:role/cdk-*-file-publishing-role-*` |
295+
| `sts:AssumeRole` | `arn:aws:iam::ACCOUNT_ID:role/cdk-*-image-publishing-role-*` |
296+
| `sts:AssumeRole` | `arn:aws:iam::ACCOUNT_ID:role/cdk-*-lookup-role-*` |
248297

249298
### Core
250299

@@ -470,7 +519,12 @@ actions used today.
470519
Ready-to-use IAM policy documents are provided alongside this guide:
471520

472521
- [iam-policy-user.json](./policies/iam-policy-user.json) -- Attach to your IAM user or role. Covers all direct SDK
473-
calls and CDK bootstrap role assumption.
522+
calls and CDK bootstrap role assumption. Replace `ACCOUNT_ID` with your account number.
474523
- [iam-policy-cfn-execution.json](./policies/iam-policy-cfn-execution.json) -- Use as the CloudFormation execution role
475-
policy if your organization scopes down the default `AdministratorAccess`. Pass it during bootstrap with
524+
policy if your organization scopes down the default `AdministratorAccess`. Includes deny statements to enforce
525+
permission boundaries. Replace `ACCOUNT_ID` with your account number. Pass it during bootstrap with
476526
`--cloudformation-execution-policies`.
527+
- [iam-policy-boundary.json](./policies/iam-policy-boundary.json) -- Permission boundary for execution roles created
528+
during deployment. Caps what agent runtimes, memories, and gateways can do at runtime (model access, logging,
529+
tracing). Replace `ACCOUNT_ID` with your account number. See
530+
[Hardening with permission boundaries](#hardening-with-permission-boundaries).
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"Version": "2012-10-17",
3+
"Statement": [
4+
{
5+
"Sid": "BedrockModelAccess",
6+
"Effect": "Allow",
7+
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
8+
"Resource": ["arn:aws:bedrock:*::foundation-model/*", "arn:aws:bedrock:*:ACCOUNT_ID:*"]
9+
},
10+
{
11+
"Sid": "CloudWatchLogs",
12+
"Effect": "Allow",
13+
"Action": [
14+
"logs:CreateLogGroup",
15+
"logs:CreateLogStream",
16+
"logs:PutLogEvents",
17+
"logs:DescribeLogGroups",
18+
"logs:DescribeLogStreams"
19+
],
20+
"Resource": "arn:aws:logs:*:ACCOUNT_ID:log-group:/aws/bedrock-agentcore/*"
21+
},
22+
{
23+
"Sid": "XRayTracing",
24+
"Effect": "Allow",
25+
"Action": [
26+
"xray:PutTraceSegments",
27+
"xray:PutTelemetryRecords",
28+
"xray:GetSamplingRules",
29+
"xray:GetSamplingTargets"
30+
],
31+
"Resource": "*"
32+
},
33+
{
34+
"Sid": "CloudWatchMetrics",
35+
"Effect": "Allow",
36+
"Action": "cloudwatch:PutMetricData",
37+
"Resource": "*",
38+
"Condition": {
39+
"StringEquals": {
40+
"cloudwatch:namespace": "bedrock-agentcore"
41+
}
42+
}
43+
},
44+
{
45+
"Sid": "AgentCoreIdentity",
46+
"Effect": "Allow",
47+
"Action": "bedrock-agentcore:GetWorkloadAccessToken*",
48+
"Resource": "arn:aws:bedrock-agentcore:*:ACCOUNT_ID:workload-identity-directory/*"
49+
}
50+
]
51+
}

docs/policies/iam-policy-cfn-execution.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,35 @@
151151
"Action": "ssm:GetParameters",
152152
"Resource": "arn:aws:ssm:*:*:parameter/cdk-bootstrap/*/version"
153153
},
154+
{
155+
"Sid": "ForceExecutionRoleBoundary",
156+
"Effect": "Deny",
157+
"Action": "iam:CreateRole",
158+
"Resource": "*",
159+
"Condition": {
160+
"StringNotEquals": {
161+
"iam:PermissionsBoundary": "arn:aws:iam::ACCOUNT_ID:policy/AgentCoreExecutionRoleBoundary"
162+
}
163+
}
164+
},
165+
{
166+
"Sid": "PreventBoundaryRemoval",
167+
"Effect": "Deny",
168+
"Action": ["iam:DeleteRolePermissionsBoundary", "iam:PutRolePermissionsBoundary"],
169+
"Resource": "*"
170+
},
171+
{
172+
"Sid": "PreventBoundaryPolicyTampering",
173+
"Effect": "Deny",
174+
"Action": [
175+
"iam:CreatePolicy",
176+
"iam:CreatePolicyVersion",
177+
"iam:DeletePolicy",
178+
"iam:DeletePolicyVersion",
179+
"iam:SetDefaultPolicyVersion"
180+
],
181+
"Resource": "arn:aws:iam::ACCOUNT_ID:policy/AgentCoreExecutionRoleBoundary"
182+
},
154183
{
155184
"Sid": "SecretsManagerForCredentials",
156185
"Effect": "Allow",

docs/policies/iam-policy-user.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"Effect": "Allow",
77
"Action": "sts:AssumeRole",
88
"Resource": [
9-
"arn:aws:iam::*:role/cdk-*-deploy-role-*",
10-
"arn:aws:iam::*:role/cdk-*-file-publishing-role-*",
11-
"arn:aws:iam::*:role/cdk-*-image-publishing-role-*",
12-
"arn:aws:iam::*:role/cdk-*-lookup-role-*"
9+
"arn:aws:iam::ACCOUNT_ID:role/cdk-*-deploy-role-*",
10+
"arn:aws:iam::ACCOUNT_ID:role/cdk-*-file-publishing-role-*",
11+
"arn:aws:iam::ACCOUNT_ID:role/cdk-*-image-publishing-role-*",
12+
"arn:aws:iam::ACCOUNT_ID:role/cdk-*-lookup-role-*"
1313
]
1414
},
1515
{

0 commit comments

Comments
 (0)