Skip to content
Merged
12 changes: 12 additions & 0 deletions packages/@aws-cdk/aws-kms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,15 @@ const key = new kms.Key(this, 'MyKey', {
runs the risk of the key becoming unmanageable if that user or role is deleted.
It is highly recommended that the key policy grants access to the account root, rather than specific principals.
See https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html for more information.

### HMAC specific key policies

HMAC keys have a different key policy than other KMS keys. They have a policy for generating and for verifying a MAC.
The respective policies can be attached to a principal via the `grantGenerateMac` and `grantVerifyMac` methods.

```ts
const key = new kms.Key(this, 'MyKey');
const user = new iam.User(this, 'MyUser');
key.grantGenerateMac(user); // Adds 'kms:GenerateMac' to the principal's policy
key.grantVerifyMac(user); // Adds 'kms:VerifyMac' to the principal's policy
```
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-kms/lib/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ abstract class AliasBase extends Resource implements IAlias {
public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant {
return this.aliasTargetKey.grantEncryptDecrypt(grantee);
}

grantGenerateMac(grantee: iam.IGrantable): iam.Grant {
return this.aliasTargetKey.grantGenerateMac(grantee);
}

grantVerifyMac(grantee: iam.IGrantable): iam.Grant {
return this.aliasTargetKey.grantVerifyMac(grantee);
}
}

/**
Expand Down Expand Up @@ -160,6 +168,8 @@ export class Alias extends AliasBase {
public grantDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
public grantEncrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
public grantGenerateMac(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
public grantVerifyMac(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
}

return new Import(scope, id);
Expand Down
102 changes: 101 additions & 1 deletion packages/@aws-cdk/aws-kms/lib/key.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import * as iam from '@aws-cdk/aws-iam';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, ResourceProps, Stack, Duration, Token, ContextProvider, Arn, ArnFormat } from '@aws-cdk/core';
import {
Arn,
ArnFormat,
ContextProvider,
Duration,
FeatureFlags,
IResource,
Lazy,
RemovalPolicy,
Resource,
ResourceProps,
Stack,
Token,
} from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import { Alias } from './alias';
Expand Down Expand Up @@ -60,6 +73,16 @@ export interface IKey extends IResource {
* Grant encryption and decryption permissions using this key to the given principal
*/
grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant;

/**
* Grant permissions to generating MACs to the given principal
*/
grantGenerateMac(grantee: iam.IGrantable): iam.Grant

/**
* Grant permissions to verifying MACs to the given principal
*/
grantVerifyMac(grantee: iam.IGrantable): iam.Grant
}

abstract class KeyBase extends Resource implements IKey {
Expand Down Expand Up @@ -193,6 +216,20 @@ abstract class KeyBase extends Resource implements IKey {
return this.grant(grantee, ...[...perms.DECRYPT_ACTIONS, ...perms.ENCRYPT_ACTIONS]);
}

/**
* Grant permissions to generating MACs to the given principal
*/
public grantGenerateMac(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee, ...perms.GENERATE_HMAC_ACTIONS);
}

/**
* Grant permissions to verifying MACs to the given principal
*/
public grantVerifyMac(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee, ...perms.VERIFY_HMAC_ACTIONS);
}

/**
* Checks whether the grantee belongs to a stack that will be deployed
* after the stack containing this key.
Expand Down Expand Up @@ -300,6 +337,41 @@ export enum KeySpec {
* Valid usage: SIGN_VERIFY
*/
ECC_SECG_P256K1 = 'ECC_SECG_P256K1',

/**
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA224.
*
* Valid usage: GENERATE_VERIFY_MAC
*/
HMAC_224 = 'HMAC_224',

/**
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA256.
*
* Valid usage: GENERATE_VERIFY_MAC
*/
HMAC_256 = 'HMAC_256',

/**
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA384.
*
* Valid usage: GENERATE_VERIFY_MAC
*/
HMAC_384 = 'HMAC_384',

/**
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA512.
*
* Valid usage: GENERATE_VERIFY_MAC
*/
HMAC_512 = 'HMAC_512',

/**
* Elliptic curve key spec available only in China Regions.
*
* Valid usage: ENCRYPT_DECRYPT and SIGN_VERIFY
*/
SM2 = 'SM2',
}

/**
Expand All @@ -315,6 +387,11 @@ export enum KeyUsage {
* Signing and verification
*/
SIGN_VERIFY = 'SIGN_VERIFY',

/**
* Generating and verifying MACs
*/
GENERATE_VERIFY_MAC = 'GENERATE_VERIFY_MAC',
}

/**
Expand Down Expand Up @@ -595,9 +672,28 @@ export class Key extends KeyBase {
KeySpec.ECC_NIST_P384,
KeySpec.ECC_NIST_P521,
KeySpec.ECC_SECG_P256K1,
KeySpec.HMAC_224,
KeySpec.HMAC_256,
KeySpec.HMAC_384,
KeySpec.HMAC_512,
],
[KeyUsage.SIGN_VERIFY]: [
KeySpec.SYMMETRIC_DEFAULT,
KeySpec.HMAC_224,
KeySpec.HMAC_256,
KeySpec.HMAC_384,
KeySpec.HMAC_512,
],
[KeyUsage.GENERATE_VERIFY_MAC]: [
KeySpec.RSA_2048,
KeySpec.RSA_3072,
KeySpec.RSA_4096,
KeySpec.ECC_NIST_P256,
KeySpec.ECC_NIST_P384,
KeySpec.ECC_NIST_P521,
KeySpec.ECC_SECG_P256K1,
KeySpec.SYMMETRIC_DEFAULT,
KeySpec.SM2,
],
};
const keySpec = props.keySpec ?? KeySpec.SYMMETRIC_DEFAULT;
Expand All @@ -606,6 +702,10 @@ export class Key extends KeyBase {
throw new Error(`key spec '${keySpec}' is not valid with usage '${keyUsage}'`);
}

if (keySpec.startsWith('HMAC') && props.enableKeyRotation) {
throw new Error('key rotation cannot be enabled on HMAC keys');
}

if (keySpec !== KeySpec.SYMMETRIC_DEFAULT && props.enableKeyRotation) {
throw new Error('key rotation cannot be enabled on asymmetric keys');
}
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-kms/lib/private/perms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ export const ENCRYPT_ACTIONS = [
export const DECRYPT_ACTIONS = [
'kms:Decrypt',
];

export const GENERATE_HMAC_ACTIONS = [
'kms:GenerateMac',
];

export const VERIFY_HMAC_ACTIONS = [
'kms:VerifyMac',
];
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-kms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/cloud-assembly-schema": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
Expand Down
48 changes: 48 additions & 0 deletions packages/@aws-cdk/aws-kms/test/alias.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Template } from '@aws-cdk/assertions';
import { ArnPrincipal, PolicyStatement } from '@aws-cdk/aws-iam';
import * as iam from '@aws-cdk/aws-iam';
import { App, CfnOutput, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { Alias } from '../lib/alias';
Expand Down Expand Up @@ -215,3 +216,50 @@ test('fails if alias policy is invalid', () => {

expect(() => app.synth()).toThrow(/A PolicyStatement must specify at least one \'action\' or \'notAction\'/);
});

test('grants generate mac to the alias target key', () => {
const app = new App();
const stack = new Stack(app, 'my-stack');
const key = new Key(stack, 'Key');
const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' });
const user = new iam.User(stack, 'User');

alias.grantGenerateMac(user);

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'kms:GenerateMac',
Effect: 'Allow',
Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] },
},
],
Version: '2012-10-17',
},
});
});

test('grants generate mac to the alias target key', () => {
const app = new App();
const stack = new Stack(app, 'my-stack');
const key = new Key(stack, 'Key');
const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' });
const user = new iam.User(stack, 'User');

alias.grantVerifyMac(user);

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'kms:VerifyMac',
Effect: 'Allow',
Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] },
},
],
Version: '2012-10-17',
},
});
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "29.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "HmacIntegTestDefaultTestDeployAssert10C19CC4.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "29.0.0",
"files": {
"9842a0bf1dbc91e927911dc4706da97c827206f07263b9d3ba005b2e5cebfc61": {
"source": {
"path": "aws-cdk-kms-hmac.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "9842a0bf1dbc91e927911dc4706da97c827206f07263b9d3ba005b2e5cebfc61.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading