Skip to content

Commit 996b6e1

Browse files
committed
First round of PR feedback
1 parent 8e78738 commit 996b6e1

File tree

7 files changed

+221
-80
lines changed

7 files changed

+221
-80
lines changed

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

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ key.addAlias('alias/bar');
3131

3232
## Sharing keys between stacks
3333

34-
> see Trust Account Identities for additional details
35-
3634
To use a KMS key in a different stack in the same CDK application,
3735
pass the construct to the other stack:
3836

@@ -41,8 +39,6 @@ pass the construct to the other stack:
4139

4240
## Importing existing keys
4341

44-
> see Trust Account Identities for additional details
45-
4642
To use a KMS key that is not defined in this CDK app, but is created through other means, use
4743
`Key.fromKeyArn(parent, name, ref)`:
4844

@@ -72,71 +68,96 @@ Note that calls to `addToResourcePolicy` and `grant*` methods on `myKeyAlias` wi
7268
no-ops, and `addAlias` and `aliasTargetKey` will fail, as the imported alias does not
7369
have a reference to the underlying KMS Key.
7470

75-
## Trust Account Identities
71+
## Key Policies
7672

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).
73+
Controlling access and usage of KMS Keys requires the use of key policies (resource-based policies attached to the key);
74+
this is in contrast to most other AWS resources where access can be entirely controlled with IAM policies,
75+
and optionally complemented with resource policies. For more in-depth understanding of KMS key access and policies, see
7976

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:
77+
* https://docs.aws.amazon.com/kms/latest/developerguide/control-access-overview.html
78+
* https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html
79+
80+
KMS keys can be created to trust IAM policies. This is the default behavior for both the KMS APIs and in
81+
the console. This behavior is enabled by the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag,
82+
which is set for all new projects; for existing projects, this same behavior can be enabled by:
8283

8384
```ts
84-
new Key(stack, 'MyKey', { trustAccountIdentities: true });
85+
new kms.Key(stack, 'MyKey', { trustAccountIdentities: true });
8586
```
8687

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
90-
default encryption which is later accessed by IAM roles in other stacks.
91-
92-
stack-1 (bucket and key created)
93-
94-
```ts
95-
// ... snip
96-
const myKmsKey = new kms.Key(this, 'MyKey', { trustAccountIdentities: true });
88+
With either the `defaultKeyPolicies` feature flag set, or `trustAccountIdentities` set,
89+
the Key will be given the following default key policy:
9790

98-
const bucket = new Bucket(this, 'MyEncryptedBucket', {
99-
bucketName: 'myEncryptedBucket',
100-
encryption: BucketEncryption.KMS,
101-
encryptionKey: myKmsKey
102-
});
91+
```json
92+
{
93+
"Effect": "Allow",
94+
"Principal": {"AWS": "arn:aws:iam::111122223333:root"},
95+
"Action": "kms:*",
96+
"Resource": "*"
97+
}
10398
```
10499

105-
stack-2 (lambda that operates on bucket and key)
100+
This policy grants full access to the key to the root account user.
101+
This enables the root account user -- via IAM policies -- to grant access to other IAM principals.
102+
With the above default policy, future permissions can be added to either the key policy or IAM principal policy.
106103

107104
```ts
108-
// ... snip
105+
const key = new kms.Key(stack, 'MyKey');
106+
const user = new iam.user(stack, 'MyUser');
107+
key.grantEncrypt(user); // Adds encrypt permissions to user policy; key policy is unmodified.
108+
```
109109

110-
const fn = new lambda.Function(this, 'MyFunction', {
111-
runtime: lambda.Runtime.NODEJS_10_X,
112-
handler: 'index.handler',
113-
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')),
114-
});
110+
Adopting the default KMS key policy (and so trusting account identities)
111+
solves many issues around cyclic dependencies between stacks.
112+
Without this default key policy, future permissions must be added to both the key policy and IAM principal policy,
113+
which can cause cyclic dependencies if the permissions cross stack boundaries.
114+
(For example, an encrypted bucket in one stack, and Lambda function that accesses it in another).
115115

116-
const bucket = s3.Bucket.fromBucketName(this, 'BucketId', 'myEncryptedBucket');
116+
### Appending to or replacing the default key policy
117117

118-
const key = kms.Key.fromKeyArn(this, 'KeyId', 'arn:aws:...'); // key ARN passed via stack props
118+
The default key policy can be ammended or replaced entirely, depending on your use case and requirements.
119+
A common addition to the key policy would be to add other key admins that are allowed to administer the key
120+
(e.g., change permissions, revoke, delete). Additional key admins can be specified at key creation or after
121+
via the `addAdmin` method.
119122

120-
bucket.grantReadWrite(fn);
121-
key.grantEncryptDecrypt(fn);
123+
```ts
124+
const myTrustedAdminRole = iam.Role.fromRoleArn(stack, 'TrustedRole', 'arn:aws:iam:....');
125+
const key = new kms.Key(stack, 'MyKey', {
126+
admins: [myTrustedAdminRole],
127+
});
128+
129+
const secondKey = new kms.Key(stack, 'MyKey2');
130+
secondKey.addAdmin(myTrustedAdminRole);
122131
```
123132

124-
The challenge in this scenario is the KMS key policy behavior. The simple way to understand
125-
this, is IAM policies for account entities can only grant the permissions granted to the
126-
account root principle in the key policy. When `trustAccountIdentities` is true,
127-
the following policy statement is added:
133+
Alternatively, a custom key policy can be specified, which will replace the default key policy.
128134

129-
```json
130-
{
131-
"Sid": "Enable IAM User Permissions",
132-
"Effect": "Allow",
133-
"Principal": {"AWS": "arn:aws:iam::111122223333:root"},
134-
"Action": "kms:*",
135-
"Resource": "*"
136-
}
135+
> **Note**: In applications without the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag set,
136+
or `trustedAccountIdentities` set to false, specifying a policy at key creation _appends_ the
137+
provided policy to the default key policy, rather than _replacing_ the default policy.
138+
139+
```ts
140+
const myTrustedAdminRole = iam.Role.fromRoleArn(stack, 'TrustedRole', 'arn:aws:iam:....');
141+
// Creates a limited admin policy and assigns to the account root.
142+
const myCustomPolicy = new iam.PolicyDocument({
143+
statements: [new iam.PolicyStatement({
144+
actions: [
145+
'kms:Create*',
146+
'kms:Describe*',
147+
'kms:Enable*',
148+
'kms:List*',
149+
'kms:Put*',
150+
],
151+
principals: [new iam.AccountRootPrincipal()],
152+
resources: ['*'],
153+
})],
154+
});
155+
const key = new kms.Key(stack, 'MyKey', {
156+
policy: myCustomPolicy,
157+
});
137158
```
138159

139-
As the name suggests this trusts IAM policies to control access to the key.
140-
If account root does not have permissions to the specific actions, then the key
141-
policy and the IAM policy for the entity (e.g. Lambda) both need to grant
142-
permission.
160+
> **Warning:** Replacing the default key policy with one that only grants access to a specific user or role
161+
runs the risk of the key becoming unmanageable if that user or role is deleted.
162+
It is highly recommended that the key policy grants access to the account root, rather than specific principals.
163+
See https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html for more information.

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

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as iam from '@aws-cdk/aws-iam';
2-
import { Annotations, FeatureFlags, IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/core';
2+
import { FeatureFlags, IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/core';
33
import * as cxapi from '@aws-cdk/cx-api';
44
import { IConstruct, Construct } from 'constructs';
55
import { Alias } from './alias';
66
import { CfnKey } from './kms.generated';
7-
import * as perms from './perms';
7+
import * as perms from './private/perms';
88

99
/**
1010
* A KMS Key, either managed by this CDK app, or imported.
@@ -294,6 +294,18 @@ export interface KeyProps {
294294
*/
295295
readonly policy?: iam.PolicyDocument;
296296

297+
/**
298+
* A list of principals to add as key administrators to the key policy.
299+
*
300+
* Key administrators have permissions to manage the key (e.g., change permissions, revoke), but do not have permissions
301+
* to use the key in cryptographic operations (e.g., encrypt, decrypt).
302+
*
303+
* These principals will be added to the default key policy (if none specified), or to the specified policy (if provided).
304+
*
305+
* @default []
306+
*/
307+
readonly admins?: iam.IPrincipal[];
308+
297309
/**
298310
* Whether the encryption key should be retained when it is removed from the Stack. This is useful when one wants to
299311
* retain access to data that was encrypted with a key that is being retired.
@@ -315,6 +327,7 @@ export interface KeyProps {
315327
*
316328
* @default - false, unless the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag is set.
317329
* @see https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-root-enable-iam
330+
* @deprecated redundant with the '@aws-cdk/aws-kms:defaultKeyPolicies' feature flag
318331
*/
319332
readonly trustAccountIdentities?: boolean;
320333
}
@@ -365,28 +378,30 @@ export class Key extends KeyBase {
365378
constructor(scope: Construct, id: string, props: KeyProps = {}) {
366379
super(scope, id);
367380

368-
const defaultKeyPoliciesEnabled = FeatureFlags.of(this).isEnabled(cxapi.KMS_DEFAULT_KEY_POLICIES);
381+
const defaultKeyPoliciesFeatureEnabled = FeatureFlags.of(this).isEnabled(cxapi.KMS_DEFAULT_KEY_POLICIES);
369382

370383
this.policy = props.policy ?? new iam.PolicyDocument();
371-
if (defaultKeyPoliciesEnabled) {
384+
if (defaultKeyPoliciesFeatureEnabled) {
372385
if (props.trustAccountIdentities === false) {
373-
Annotations.of(this).addWarning('`trustAccountIdentities` has no impact if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set');
386+
throw new Error('`trustAccountIdentities` cannot be false if the @aws-cdk/aws-kms:defaultKeyPolicies feature flag is set');
374387
}
375388

376389
this.trustAccountIdentities = true;
377390
// Set the default key policy if one hasn't been provided by the user.
378391
if (!props.policy) {
379-
this.grantDefaultKeyPolicy();
392+
this.addDefaultAdminPolicy();
380393
}
381394
} else {
382-
this.trustAccountIdentities = props.trustAccountIdentities || false;
395+
this.trustAccountIdentities = props.trustAccountIdentities ?? false;
383396
if (this.trustAccountIdentities) {
384-
this.grantDefaultKeyPolicy();
397+
this.addDefaultAdminPolicy();
385398
} else {
386-
this.grantAccountAdminPrivilegesPlusGenerateDataKey();
399+
this.addLegacyAdminPolicy();
387400
}
388401
}
389402

403+
(props.admins ?? []).forEach((p) => this.addAdmin(p));
404+
390405
const resource = new CfnKey(this, 'Resource', {
391406
description: props.description,
392407
enableKeyRotation: props.enableKeyRotation,
@@ -403,13 +418,27 @@ export class Key extends KeyBase {
403418
}
404419
}
405420

421+
/**
422+
* Adds the specified principal to the key policy as a key administrator.
423+
*
424+
* Key administrators have permissions to manage the key (e.g., change permissions, revoke), but do not have permissions
425+
* to use the key in cryptographic operations (e.g., encrypt, decrypt).
426+
*/
427+
public addAdmin(principal: iam.IPrincipal) {
428+
this.addToResourcePolicy(new iam.PolicyStatement({
429+
resources: ['*'],
430+
actions: perms.ADMIN_ACTIONS,
431+
principals: [principal],
432+
}));
433+
}
434+
406435
/**
407436
* Adds the default key policy to the key. This policy gives the AWS account (root user) full access to the CMK,
408437
* which reduces the risk of the CMK becoming unmanageable and enables IAM policies to allow access to the CMK.
409438
* This is the same policy that is default when creating a Key via the KMS API or Console.
410439
* @see https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default
411440
*/
412-
private grantDefaultKeyPolicy() {
441+
private addDefaultAdminPolicy() {
413442
this.addToResourcePolicy(new iam.PolicyStatement({
414443
resources: ['*'],
415444
actions: ['kms:*'],
@@ -426,10 +455,30 @@ export class Key extends KeyBase {
426455
* @link https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default
427456
* @deprecated
428457
*/
429-
private grantAccountAdminPrivilegesPlusGenerateDataKey() {
458+
private addLegacyAdminPolicy() {
459+
// This is equivalent to `[...perms.ADMIN_ACTIONS, 'kms:GenerateDataKey']`,
460+
// but keeping this explicit ordering for backwards-compatibility (changing the ordering causes resource updates)
461+
const actions = [
462+
'kms:Create*',
463+
'kms:Describe*',
464+
'kms:Enable*',
465+
'kms:List*',
466+
'kms:Put*',
467+
'kms:Update*',
468+
'kms:Revoke*',
469+
'kms:Disable*',
470+
'kms:Get*',
471+
'kms:Delete*',
472+
'kms:ScheduleKeyDeletion',
473+
'kms:CancelKeyDeletion',
474+
'kms:GenerateDataKey',
475+
'kms:TagResource',
476+
'kms:UntagResource',
477+
];
478+
430479
this.addToResourcePolicy(new iam.PolicyStatement({
431480
resources: ['*'],
432-
actions: [...perms.ADMIN_ACTIONS, 'kms:GenerateDataKey'],
481+
actions,
433482
principals: [new iam.AccountRootPrincipal()],
434483
}));
435484
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
"license": "Apache-2.0",
7575
"devDependencies": {
7676
"@aws-cdk/assert": "0.0.0",
77-
"@aws-cdk/cloud-assembly-schema": "0.0.0",
7877
"cdk-build-tools": "0.0.0",
7978
"cdk-integ-tools": "0.0.0",
8079
"cfn2ts": "0.0.0",

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",
2321
"kms:ScheduleKeyDeletion",
2422
"kms:CancelKeyDeletion",
25-
"kms:GenerateDataKey"
23+
"kms:GenerateDataKey",
24+
"kms:TagResource",
25+
"kms:UntagResource"
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",
2220
"kms:ScheduleKeyDeletion",
2321
"kms:CancelKeyDeletion",
24-
"kms:GenerateDataKey"
22+
"kms:GenerateDataKey",
23+
"kms:TagResource",
24+
"kms:UntagResource"
2525
],
2626
"Effect": "Allow",
2727
"Principal": {
@@ -74,4 +74,4 @@
7474
}
7575
}
7676
}
77-
}
77+
}

0 commit comments

Comments
 (0)