Skip to content

Commit 9076d6e

Browse files
GavinZZpaulhcsun
andauthored
fix(kms): kms key grant methods misidentify region when enclosing stack is different region (#29315)
### Issue # (if applicable) Closes #29308 ### Reason for this change This problem is grant() determines the region of a Key using Stack.of(key).region, however the enclosing Stack's region may differ to that of the actual resource. When this happens, the IAM policy generated allows a `*` resource which is against the least privilege rule. ### Description of changes KMS key already has `env` value on account and region, use this first. If not exist, use stack account and region. ### Description of how you validated changes New unit test ### Checklist - [X] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --------- Co-authored-by: paulhcsun <[email protected]>
1 parent fac4a9c commit 9076d6e

File tree

5 files changed

+120
-1
lines changed

5 files changed

+120
-1
lines changed

packages/aws-cdk-lib/aws-kms/lib/key.ts

+12
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,12 @@ abstract class KeyBase extends Resource implements IKey {
262262
}
263263
const bucketStack = Stack.of(this);
264264
const identityStack = Stack.of(grantee.grantPrincipal);
265+
266+
if (FeatureFlags.of(this).isEnabled(cxapi.KMS_REDUCE_CROSS_ACCOUNT_REGION_POLICY_SCOPE)) {
267+
// if two compared stacks have the same region, this should return 'false' since it's from the
268+
// same region; if two stacks have different region, then compare env.region
269+
return bucketStack.region !== identityStack.region && this.env.region !== identityStack.region;
270+
}
265271
return bucketStack.region !== identityStack.region;
266272
}
267273

@@ -271,6 +277,12 @@ abstract class KeyBase extends Resource implements IKey {
271277
}
272278
const bucketStack = Stack.of(this);
273279
const identityStack = Stack.of(grantee.grantPrincipal);
280+
281+
if (FeatureFlags.of(this).isEnabled(cxapi.KMS_REDUCE_CROSS_ACCOUNT_REGION_POLICY_SCOPE)) {
282+
// if two compared stacks have the same region, this should return 'false' since it's from the
283+
// same region; if two stacks have different region, then compare env.account
284+
return bucketStack.account !== identityStack.account && this.env.account !== identityStack.account;
285+
}
274286
return bucketStack.account !== identityStack.account;
275287
}
276288
}

packages/aws-cdk-lib/aws-kms/test/key.test.ts

+61
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describeDeprecated } from '@aws-cdk/cdk-build-tools';
22
import { Match, Template } from '../../assertions';
33
import * as iam from '../../aws-iam';
44
import * as cdk from '../../core';
5+
import * as cxapi from '../../cx-api';
56
import * as kms from '../lib';
67
import { KeySpec, KeyUsage } from '../lib';
78

@@ -81,6 +82,66 @@ describe('key policies', () => {
8182
});
8283
});
8384

85+
test('cross region key with iam role grant', () => {
86+
const app = new cdk.App({ context: { [cxapi.KMS_REDUCE_CROSS_ACCOUNT_REGION_POLICY_SCOPE]: true } });
87+
const stack = new cdk.Stack(app, 'test-stack', { env: { account: '000000000000', region: 'us-west-2' } });
88+
const key = kms.Key.fromKeyArn(
89+
stack,
90+
'Key',
91+
'arn:aws:kms:eu-north-1:000000000000:key/e3ab59e5-3dc3-4bc4-9c3f-c790231d2287',
92+
);
93+
94+
const roleStack = new cdk.Stack(app, 'RoleStack', {
95+
env: { account: '000000000000', region: 'eu-north-1' },
96+
});
97+
const role = new iam.Role(roleStack, 'Role', {
98+
assumedBy: new iam.AccountPrincipal('000000000000'),
99+
});
100+
key.grantEncryptDecrypt(role);
101+
102+
Template.fromStack(roleStack).hasResourceProperties('AWS::IAM::Policy', {
103+
PolicyDocument: {
104+
Statement: [
105+
{
106+
Effect: 'Allow',
107+
Resource: 'arn:aws:kms:eu-north-1:000000000000:key/e3ab59e5-3dc3-4bc4-9c3f-c790231d2287',
108+
},
109+
],
110+
Version: '2012-10-17',
111+
},
112+
});
113+
});
114+
115+
test('cross region key with iam role grant when feature flag is disabled', () => {
116+
const app = new cdk.App({ context: { [cxapi.KMS_REDUCE_CROSS_ACCOUNT_REGION_POLICY_SCOPE]: false } });
117+
const stack = new cdk.Stack(app, 'test-stack', { env: { account: '000000000000', region: 'us-west-2' } });
118+
const key = kms.Key.fromKeyArn(
119+
stack,
120+
'Key',
121+
'arn:aws:kms:eu-north-1:000000000000:key/e3ab59e5-3dc3-4bc4-9c3f-c790231d2287',
122+
);
123+
124+
const roleStack = new cdk.Stack(app, 'RoleStack', {
125+
env: { account: '000000000000', region: 'eu-north-1' },
126+
});
127+
const role = new iam.Role(roleStack, 'Role', {
128+
assumedBy: new iam.AccountPrincipal('000000000000'),
129+
});
130+
key.grantEncryptDecrypt(role);
131+
132+
Template.fromStack(roleStack).hasResourceProperties('AWS::IAM::Policy', {
133+
PolicyDocument: {
134+
Statement: [
135+
{
136+
Effect: 'Allow',
137+
Resource: '*',
138+
},
139+
],
140+
Version: '2012-10-17',
141+
},
142+
});
143+
});
144+
84145
test('can append to the default key policy', () => {
85146
const stack = new cdk.Stack();
86147
const statement = new iam.PolicyStatement({ resources: ['*'], actions: ['kms:Put*'] });

packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Flags come in three types:
6666
| [@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction](#aws-cdkaws-cloudwatch-actionschangelambdapermissionlogicalidforlambdaaction) | When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID. | 2.124.0 | (fix) |
6767
| [@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse](#aws-cdkaws-codepipelinecrossaccountkeysdefaultvaluetofalse) | Enables Pipeline to set the default value for crossAccountKeys to false. | 2.127.0 | (default) |
6868
| [@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2](#aws-cdkaws-codepipelinedefaultpipelinetypetov2) | Enables Pipeline to set the default pipeline type to V2. | V2NEXT | (default) |
69+
| [@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope](#aws-cdkaws-kmsreducecrossaccountregionpolicyscope) | When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only. | V2NEXT | (fix) |
6970

7071
<!-- END table -->
7172

@@ -122,7 +123,8 @@ The following json shows the current recommended set of flags, as `cdk init` wou
122123
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
123124
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
124125
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
125-
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true
126+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
127+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true
126128
}
127129
}
128130
```
@@ -1249,4 +1251,18 @@ construct, the construct automatically defaults the value of this property to `P
12491251
**Compatibility with old behavior:** Pass `pipelineType: PipelineType.V1` to `Pipeline` construct to restore the previous behavior.
12501252

12511253

1254+
### @aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope
1255+
1256+
*When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only.* (fix)
1257+
1258+
When this feature flag is enabled and calling KMS key grant method, the created IAM policy will reduce the resource scope from
1259+
'*' to this specific granting KMS key.
1260+
1261+
1262+
| Since | Default | Recommended |
1263+
| ----- | ----- | ----- |
1264+
| (not in v1) | | |
1265+
| V2NEXT | `false` | `true` |
1266+
1267+
12521268
<!-- END details -->

packages/aws-cdk-lib/cx-api/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,20 @@ _cdk.json_
292292
}
293293
}
294294
```
295+
296+
* `@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope`
297+
298+
Reduce resource scope of the IAM Policy created from KMS key grant to granting key only.
299+
300+
When this feature flag is enabled and calling KMS key grant method, the created IAM policy will reduce the resource scope from
301+
'*' to this specific granting KMS key.
302+
303+
_cdk.json_
304+
305+
```json
306+
{
307+
"context": {
308+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true
309+
}
310+
}
311+
```

packages/aws-cdk-lib/cx-api/lib/features.ts

+13
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export const CODECOMMIT_SOURCE_ACTION_DEFAULT_BRANCH_NAME = '@aws-cdk/aws-codepi
100100
export const LAMBDA_PERMISSION_LOGICAL_ID_FOR_LAMBDA_ACTION = '@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction';
101101
export const CODEPIPELINE_CROSS_ACCOUNT_KEYS_DEFAULT_VALUE_TO_FALSE = '@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse';
102102
export const CODEPIPELINE_DEFAULT_PIPELINE_TYPE_TO_V2 = '@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2';
103+
export const KMS_REDUCE_CROSS_ACCOUNT_REGION_POLICY_SCOPE = '@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope';
103104

104105
export const FLAGS: Record<string, FlagInfo> = {
105106
//////////////////////////////////////////////////////////////////////
@@ -1021,6 +1022,18 @@ export const FLAGS: Record<string, FlagInfo> = {
10211022
recommendedValue: true,
10221023
compatibilityWithOldBehaviorMd: 'Pass `pipelineType: PipelineType.V1` to `Pipeline` construct to restore the previous behavior.',
10231024
},
1025+
1026+
//////////////////////////////////////////////////////////////////////
1027+
[KMS_REDUCE_CROSS_ACCOUNT_REGION_POLICY_SCOPE]: {
1028+
type: FlagType.BugFix,
1029+
summary: 'When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only.',
1030+
detailsMd: `
1031+
When this feature flag is enabled and calling KMS key grant method, the created IAM policy will reduce the resource scope from
1032+
'*' to this specific granting KMS key.
1033+
`,
1034+
introducedIn: { v2: 'V2NEXT' },
1035+
recommendedValue: true,
1036+
},
10241037
};
10251038

10261039
const CURRENT_MV = 'v2';

0 commit comments

Comments
 (0)