Skip to content

Commit fe524be

Browse files
committed
feat(kms): change default key policy to align with KMS best practices (under feature flag)
In #5575, a new flag (`trustAccountIdentities`) was introduced which -- when set -- changes the default key policy from a custom key admin policy to one that grants all access to the key to the root account user. This key policy matches the default policy when a key is created via the KMS APIs or console. For backwards-compatibility reasons, the default for `trustAccountIdentities` had to be set to `false`. Without the flag explicitly set, the default key policy is one that (a) doesn't match the KMS-recommended admin policy and (b) doesn't explicitly enable IAM principal policies to acccess the key. This means that all usage operations (e.g., Encrypt, GenerateDataKey) must be added to both the key policy and to the principal policy. This change introduces a new feature flag to flip the default behavior of the `trustAccountIdentities` flag, so new keys created will have the sane defaults matching the KMS recommended best practices. As a related change, this feature flag also changes the behavior when a user passes in `policy` when creating a Key. Without the feature flag set, the policy is always appended to the default key policy. With the feature flag set, the policy will *override* the default key policy, enabling users to opt-out of the default key policy to introduce a more restrictive policy if desired. This also matches the KMS API behavior, where a policy provided by the user will override the defaults. Marking this PR as `requires-two-approvers` to ensure this PR gets an appropriately-critical review. BREAKING CHANGE: change the default value of trustAccountIdentities to true, which will result in the key getting the KMS-recommended default key policy. This is enabled through the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag. fixes #8977 fixes #10575 fixes #11309
1 parent e8c8d77 commit fe524be

File tree

8 files changed

+494
-328
lines changed

8 files changed

+494
-328
lines changed

packages/@aws-cdk/aws-kms/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,19 @@ have a reference to the underlying KMS Key.
7474

7575
## Trust Account Identities
7676

77-
KMS keys can be created to trust IAM policies. This is the default behavior in
78-
the console and is described
79-
[here](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html).
80-
This same behavior can be enabled by:
77+
KMS keys can be created to trust IAM policies. This is the default behavior for both the KMS APIs and in
78+
the console and is described [here](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html).
79+
80+
This behavior is enabled by the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag,
81+
which is set for all new projects. For existing projects, this same behavior can be enabled by:
8182

8283
```ts
8384
new Key(stack, 'MyKey', { trustAccountIdentities: true });
8485
```
8586

86-
Using `trustAccountIdentities` solves many issues around cyclic dependencies
87-
between stacks. The most common use case is creating an S3 Bucket with CMK
87+
Adopting the default KMS key policy (and so trusting account identities)
88+
solves many issues around cyclic dependencies between stacks.
89+
The most common use case is creating an S3 Bucket with CMK
8890
default encryption which is later accessed by IAM roles in other stacks.
8991

9092
stack-1 (bucket and key created)
@@ -138,5 +140,3 @@ As the name suggests this trusts IAM policies to control access to the key.
138140
If account root does not have permissions to the specific actions, then the key
139141
policy and the IAM policy for the entity (e.g. Lambda) both need to grant
140142
permission.
141-
142-

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

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as iam from '@aws-cdk/aws-iam';
2-
import { IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/core';
2+
import { Annotations, FeatureFlags, IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/core';
3+
import * as cxapi from '@aws-cdk/cx-api';
34
import { IConstruct, Construct } from 'constructs';
45
import { Alias } from './alias';
56
import { CfnKey } from './kms.generated';
7+
import * as perms from './perms';
68

79
/**
810
* A KMS Key, either managed by this CDK app, or imported.
@@ -77,8 +79,9 @@ abstract class KeyBase extends Resource implements IKey {
7779
/**
7880
* Optional property to control trusting account identities.
7981
*
80-
* If specified grants will default identity policies instead of to both
81-
* resource and identity policies.
82+
* If specified, grants will default identity policies instead of to both
83+
* resource and identity policies. This matches the default behavior when creating
84+
* KMS keys via the API or console.
8285
*/
8386
protected abstract readonly trustAccountIdentities: boolean;
8487

@@ -171,32 +174,21 @@ abstract class KeyBase extends Resource implements IKey {
171174
* Grant decryption permisisons using this key to the given principal
172175
*/
173176
public grantDecrypt(grantee: iam.IGrantable): iam.Grant {
174-
return this.grant(grantee,
175-
'kms:Decrypt',
176-
);
177+
return this.grant(grantee, ...perms.DECRYPT_ACTIONS);
177178
}
178179

179180
/**
180181
* Grant encryption permisisons using this key to the given principal
181182
*/
182183
public grantEncrypt(grantee: iam.IGrantable): iam.Grant {
183-
return this.grant(grantee,
184-
'kms:Encrypt',
185-
'kms:ReEncrypt*',
186-
'kms:GenerateDataKey*',
187-
);
184+
return this.grant(grantee, ...perms.ENCRYPT_ACTIONS);
188185
}
189186

190187
/**
191188
* Grant encryption and decryption permisisons using this key to the given principal
192189
*/
193190
public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant {
194-
return this.grant(grantee,
195-
'kms:Decrypt',
196-
'kms:Encrypt',
197-
'kms:ReEncrypt*',
198-
'kms:GenerateDataKey*',
199-
);
191+
return this.grant(grantee, ...[...perms.DECRYPT_ACTIONS, ...perms.ENCRYPT_ACTIONS]);
200192
}
201193

202194
/**
@@ -293,6 +285,10 @@ export interface KeyProps {
293285
/**
294286
* Custom policy document to attach to the KMS key.
295287
*
288+
* NOTE - If the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag is set (the default for new projects),
289+
* this policy will *override* the default key policy and become the only key policy for the key. If the
290+
* feature flag is not set, this policy will be appended to the default key policy.
291+
*
296292
* @default - A policy document with permissions for the account root to
297293
* administer the key will be created.
298294
*/
@@ -311,9 +307,13 @@ export interface KeyProps {
311307
*
312308
* Setting this to true adds a default statement which delegates key
313309
* access control completely to the identity's IAM policy (similar
314-
* to how it works for other AWS resources).
310+
* to how it works for other AWS resources). This matches the default behavior
311+
* when creating KMS keys via the API or console.
315312
*
316-
* @default false
313+
* If the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag is set (the default for new projects),
314+
* this flag will always be treated as 'true' and does not need to be explicitly set.
315+
*
316+
* @default - false, unless the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag is set.
317317
* @see https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-root-enable-iam
318318
*/
319319
readonly trustAccountIdentities?: boolean;
@@ -365,12 +365,26 @@ export class Key extends KeyBase {
365365
constructor(scope: Construct, id: string, props: KeyProps = {}) {
366366
super(scope, id);
367367

368-
this.policy = props.policy || new iam.PolicyDocument();
369-
this.trustAccountIdentities = props.trustAccountIdentities || false;
370-
if (this.trustAccountIdentities) {
371-
this.allowAccountIdentitiesToControl();
368+
const defaultKeyPoliciesEnabled = FeatureFlags.of(this).isEnabled(cxapi.KMS_DEFAULT_KEY_POLICIES);
369+
370+
this.policy = props.policy ?? new iam.PolicyDocument();
371+
if (defaultKeyPoliciesEnabled) {
372+
if (props.trustAccountIdentities === false) {
373+
Annotations.of(this).addWarning('`trustAccountIdentities` has no impact if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set');
374+
}
375+
376+
this.trustAccountIdentities = true;
377+
// Set the default key policy if one hasn't been provided by the user.
378+
if (!props.policy) {
379+
this.grantDefaultKeyPolicy();
380+
}
372381
} else {
373-
this.allowAccountToAdmin();
382+
this.trustAccountIdentities = props.trustAccountIdentities || false;
383+
if (this.trustAccountIdentities) {
384+
this.grantDefaultKeyPolicy();
385+
} else {
386+
this.grantAccountAdminPrivilegesPlusGenerateDataKey();
387+
}
374388
}
375389

376390
const resource = new CfnKey(this, 'Resource', {
@@ -389,41 +403,33 @@ export class Key extends KeyBase {
389403
}
390404
}
391405

392-
private allowAccountIdentitiesToControl() {
406+
/**
407+
* Adds the default key policy to the key. This policy gives the AWS account (root user) full access to the CMK,
408+
* which reduces the risk of the CMK becoming unmanageable and enables IAM policies to allow access to the CMK.
409+
* This is the same policy that is default when creating a Key via the KMS API or Console.
410+
* @see https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default
411+
*/
412+
private grantDefaultKeyPolicy() {
393413
this.addToResourcePolicy(new iam.PolicyStatement({
394414
resources: ['*'],
395415
actions: ['kms:*'],
396416
principals: [new iam.AccountRootPrincipal()],
397417
}));
398-
399418
}
419+
400420
/**
401-
* Let users or IAM policies from this account admin this key.
421+
* Grants the account admin privileges -- not full account access -- plus the GenerateDataKey action.
422+
* The GenerateDataKey action was added for interop with S3 in https://github.com/aws/aws-cdk/issues/3458.
423+
*
424+
* This policy is discouraged and deprecated by the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag.
425+
*
402426
* @link https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default
403-
* @link https://aws.amazon.com/premiumsupport/knowledge-center/update-key-policy-future/
427+
* @deprecated
404428
*/
405-
private allowAccountToAdmin() {
406-
const actions = [
407-
'kms:Create*',
408-
'kms:Describe*',
409-
'kms:Enable*',
410-
'kms:List*',
411-
'kms:Put*',
412-
'kms:Update*',
413-
'kms:Revoke*',
414-
'kms:Disable*',
415-
'kms:Get*',
416-
'kms:Delete*',
417-
'kms:ScheduleKeyDeletion',
418-
'kms:CancelKeyDeletion',
419-
'kms:GenerateDataKey',
420-
'kms:TagResource',
421-
'kms:UntagResource',
422-
];
423-
429+
private grantAccountAdminPrivilegesPlusGenerateDataKey() {
424430
this.addToResourcePolicy(new iam.PolicyStatement({
425431
resources: ['*'],
426-
actions,
432+
actions: [...perms.ADMIN_ACTIONS, 'kms:GenerateDataKey'],
427433
principals: [new iam.AccountRootPrincipal()],
428434
}));
429435
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html
2+
3+
export const ADMIN_ACTIONS = [
4+
'kms:Create*',
5+
'kms:Describe*',
6+
'kms:Enable*',
7+
'kms:List*',
8+
'kms:Put*',
9+
'kms:Update*',
10+
'kms:Revoke*',
11+
'kms:Disable*',
12+
'kms:Get*',
13+
'kms:Delete*',
14+
'kms:TagResource',
15+
'kms:UntagResource',
16+
'kms:ScheduleKeyDeletion',
17+
'kms:CancelKeyDeletion',
18+
];
19+
20+
export const ENCRYPT_ACTIONS = [
21+
'kms:Encrypt',
22+
'kms:ReEncrypt*',
23+
'kms:GenerateDataKey*',
24+
];
25+
26+
export const DECRYPT_ACTIONS = [
27+
'kms:Decrypt',
28+
];

packages/@aws-cdk/aws-kms/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"license": "Apache-2.0",
7575
"devDependencies": {
7676
"@aws-cdk/assert": "0.0.0",
77+
"@aws-cdk/cloud-assembly-schema": "0.0.0",
7778
"cdk-build-tools": "0.0.0",
7879
"cdk-integ-tools": "0.0.0",
7980
"cfn2ts": "0.0.0",
@@ -82,12 +83,14 @@
8283
"dependencies": {
8384
"@aws-cdk/aws-iam": "0.0.0",
8485
"@aws-cdk/core": "0.0.0",
86+
"@aws-cdk/cx-api": "0.0.0",
8587
"constructs": "^3.2.0"
8688
},
8789
"homepage": "https://github.com/aws/aws-cdk",
8890
"peerDependencies": {
8991
"@aws-cdk/aws-iam": "0.0.0",
9092
"@aws-cdk/core": "0.0.0",
93+
"@aws-cdk/cx-api": "0.0.0",
9194
"constructs": "^3.2.0"
9295
},
9396
"engines": {

packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.expected.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
"kms:Disable*",
1919
"kms:Get*",
2020
"kms:Delete*",
21+
"kms:TagResource",
22+
"kms:UntagResource",
2123
"kms:ScheduleKeyDeletion",
2224
"kms:CancelKeyDeletion",
23-
"kms:GenerateDataKey",
24-
"kms:TagResource",
25-
"kms:UntagResource"
25+
"kms:GenerateDataKey"
2626
],
2727
"Effect": "Allow",
2828
"Principal": {
@@ -80,4 +80,4 @@
8080
}
8181
}
8282
}
83-
]
83+
]

packages/@aws-cdk/aws-kms/test/integ.key.expected.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
"kms:Disable*",
1818
"kms:Get*",
1919
"kms:Delete*",
20+
"kms:TagResource",
21+
"kms:UntagResource",
2022
"kms:ScheduleKeyDeletion",
2123
"kms:CancelKeyDeletion",
22-
"kms:GenerateDataKey",
23-
"kms:TagResource",
24-
"kms:UntagResource"
24+
"kms:GenerateDataKey"
2525
],
2626
"Effect": "Allow",
2727
"Principal": {
@@ -74,4 +74,4 @@
7474
}
7575
}
7676
}
77-
}
77+
}

0 commit comments

Comments
 (0)