diff --git a/packages/@aws-cdk/aws-kms/README.md b/packages/@aws-cdk/aws-kms/README.md index 6689b5a2a7f9a..0818c4252d6b3 100644 --- a/packages/@aws-cdk/aws-kms/README.md +++ b/packages/@aws-cdk/aws-kms/README.md @@ -15,7 +15,7 @@ Defines a KMS key: ```js -new EncryptionKey(this, 'MyKey', { +new Key(this, 'MyKey', { enableKeyRotation: true }); ``` @@ -23,7 +23,7 @@ new EncryptionKey(this, 'MyKey', { Add a couple of aliases: ```js -const key = new EncryptionKey(this, 'MyKey'); +const key = new Key(this, 'MyKey'); key.addAlias('alias/foo'); key.addAlias('alias/bar'); ``` @@ -39,10 +39,10 @@ pass the construct to the other stack: ### Importing existing keys To use a KMS key that is not defined in this CDK app, but is created through other means, use -`EncryptionKey.import(parent, name, ref)`: +`Key.import(parent, name, ref)`: ```ts -const myKeyImported = EncryptionKey.import(this, 'MyImportedKey', { +const myKeyImported = Key.import(this, 'MyImportedKey', { keyArn: 'arn:aws:...' }); diff --git a/packages/@aws-cdk/aws-kms/lib/alias.ts b/packages/@aws-cdk/aws-kms/lib/alias.ts index 57c21637bdf37..f99646ec68178 100644 --- a/packages/@aws-cdk/aws-kms/lib/alias.ts +++ b/packages/@aws-cdk/aws-kms/lib/alias.ts @@ -1,24 +1,57 @@ -import { Construct } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; import { IKey } from './key'; import { CfnAlias } from './kms.generated'; const REQUIRED_ALIAS_PREFIX = 'alias/'; -const DISALLOWED_PREFIX = REQUIRED_ALIAS_PREFIX + 'AWS'; +const DISALLOWED_PREFIX = REQUIRED_ALIAS_PREFIX + 'aws/'; -export interface EncryptionKeyAliasProps { +/** + * A KMS Key alias. + */ +export interface IAlias extends IResource { + /** + * The name of the alias. + * + * @attribute AliasName + */ + readonly aliasName: string; + + /** + * The Key to which the Alias refers. + * + * @attribute TargetKeyId + */ + readonly aliasTargetKey: IKey; +} + +/** + * Construction properties for a KMS Key Alias object. + */ +export interface AliasProps { /** * The name of the alias. The name must start with alias followed by a * forward slash, such as alias/. You can't specify aliases that begin with * alias/AWS. These aliases are reserved. */ - readonly alias: string; + readonly name: string; /** * The ID of the key for which you are creating the alias. Specify the key's * globally unique identifier or Amazon Resource Name (ARN). You can't * specify another alias. */ - readonly key: IKey; + readonly targetKey: IKey; +} + +abstract class AliasBase extends Resource implements IAlias { + public abstract readonly aliasName: string; + + public abstract readonly aliasTargetKey: IKey; +} + +export interface AliasAttributes { + readonly aliasName: string; + readonly aliasTargetKey: IKey; } /** @@ -29,31 +62,46 @@ export interface EncryptionKeyAliasProps { * Working with Aliases in the AWS Key Management Service Developer Guide. * * You can also add an alias for a key by calling `key.addAlias(alias)`. + * + * @resource AWS::KMS::Alias */ -export class EncryptionKeyAlias extends Construct { - /** - * The name of the alias. - */ - public aliasName: string; +export class Alias extends AliasBase { + public static fromAliasAttributes(scope: Construct, id: string, attrs: AliasAttributes): IAlias { + // tslint:disable-next-line: class-name + class _Alias extends AliasBase { + public get aliasName() { return attrs.aliasName; } + public get aliasTargetKey() { return attrs.aliasTargetKey; } + } + return new _Alias(scope, id); + } + + public readonly aliasName: string; + public readonly aliasTargetKey: IKey; - constructor(scope: Construct, id: string, props: EncryptionKeyAliasProps) { + constructor(scope: Construct, id: string, props: AliasProps) { super(scope, id); - if (!props.alias.startsWith(REQUIRED_ALIAS_PREFIX)) { - throw new Error(`Alias must start with the prefix "${REQUIRED_ALIAS_PREFIX}": ${props.alias}`); - } + if (!Token.unresolved(props.name)) { + if (!props.name.startsWith(REQUIRED_ALIAS_PREFIX)) { + throw new Error(`Alias must start with the prefix "${REQUIRED_ALIAS_PREFIX}": ${props.name}`); + } - if (props.alias === REQUIRED_ALIAS_PREFIX) { - throw new Error(`Alias must include a value after "${REQUIRED_ALIAS_PREFIX}": ${props.alias}`); - } + if (props.name === REQUIRED_ALIAS_PREFIX) { + throw new Error(`Alias must include a value after "${REQUIRED_ALIAS_PREFIX}": ${props.name}`); + } + + if (props.name.startsWith(DISALLOWED_PREFIX)) { + throw new Error(`Alias cannot start with ${DISALLOWED_PREFIX}: ${props.name}`); + } - if (props.alias.startsWith(DISALLOWED_PREFIX)) { - throw new Error(`Alias cannot start with ${DISALLOWED_PREFIX}: ${props.alias}`); + if (!props.name.match(/^[a-zA-Z0-9:/_-]{1,256}$/)) { + throw new Error(`Alias name must be between 1 and 256 characters in a-zA-Z0-9:/_-`); + } } const resource = new CfnAlias(this, 'Resource', { - aliasName: props.alias, - targetKeyId: props.key.keyArn + aliasName: props.name, + targetKeyId: props.targetKey.keyArn }); this.aliasName = resource.aliasName; diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 0dff0c6cf1ab0..3efc794dc63b7 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,9 +1,12 @@ import iam = require('@aws-cdk/aws-iam'); import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; -import { Construct, DeletionPolicy, IResource } from '@aws-cdk/cdk'; -import { EncryptionKeyAlias } from './alias'; +import { Construct, DeletionPolicy, IResource, Resource } from '@aws-cdk/cdk'; +import { Alias } from './alias'; import { CfnKey } from './kms.generated'; +/** + * A KMS Key, either managed by this CDK app, or imported. + */ export interface IKey extends IResource { /** * The ARN of the key. @@ -15,7 +18,7 @@ export interface IKey extends IResource { /** * Defines a new alias for the key. */ - addAlias(alias: string): EncryptionKeyAlias; + addAlias(alias: string): Alias; /** * Adds a statement to the KMS key resource policy. @@ -47,7 +50,7 @@ export interface IKey extends IResource { grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant; } -abstract class KeyBase extends Construct implements IKey { +abstract class KeyBase extends Resource implements IKey { /** * The ARN of the key. */ @@ -64,8 +67,8 @@ abstract class KeyBase extends Construct implements IKey { /** * Defines a new alias for the key. */ - public addAlias(alias: string): EncryptionKeyAlias { - return new EncryptionKeyAlias(this, 'Alias', { alias, key: this }); + public addAlias(alias: string): Alias { + return new Alias(this, 'Alias', { name: alias, targetKey: this }); } /** @@ -179,9 +182,17 @@ export interface KeyProps { /** * Defines a KMS key. + * + * @resource AWS::KMS::Key */ export class Key extends KeyBase { - + /** + * Import an externally defined KMS Key using its ARN. + * + * @param scope the construct that will "own" the imported key. + * @param id the id of the imported key in the construct tree. + * @param keyArn the ARN of an existing KMS key. + */ public static fromKeyArn(scope: Construct, id: string, keyArn: string): IKey { class Import extends KeyBase { public keyArn = keyArn; diff --git a/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts b/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts index 4c2fcbacc9ad7..1395bc8a67543 100644 --- a/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts +++ b/packages/@aws-cdk/aws-kms/test/integ.key-sharing.lit.ts @@ -19,7 +19,7 @@ class KeyStack extends cdk.Stack { } interface UseStackProps extends cdk.StackProps { - key: kms.IKey; // Use IEncryptionKey here + key: kms.IKey; // Use IKey here } /** @@ -29,7 +29,7 @@ class UseStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: UseStackProps) { super(scope, id, props); - // Use the IEncryptionKey object here. + // Use the IKey object here. props.key.addAlias('alias/foo'); } } diff --git a/packages/@aws-cdk/aws-kms/test/test.alias.ts b/packages/@aws-cdk/aws-kms/test/test.alias.ts index 30547147ed52c..c9549e319f940 100644 --- a/packages/@aws-cdk/aws-kms/test/test.alias.ts +++ b/packages/@aws-cdk/aws-kms/test/test.alias.ts @@ -2,7 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Key } from '../lib'; -import { EncryptionKeyAlias } from '../lib/alias'; +import { Alias } from '../lib/alias'; export = { 'default alias'(test: Test) { @@ -10,7 +10,7 @@ export = { const stack = new Stack(app, 'Test'); const key = new Key(stack, 'Key'); - new EncryptionKeyAlias(stack, 'Alias', { key, alias: 'alias/foo' }); + new Alias(stack, 'Alias', { targetKey: key, name: 'alias/foo' }); expect(stack).to(haveResource('AWS::KMS::Alias', { AliasName: 'alias/foo', @@ -29,9 +29,9 @@ export = { enabled: false }); - test.throws(() => new EncryptionKeyAlias(stack, 'Alias', { - alias: 'foo', - key + test.throws(() => new Alias(stack, 'Alias', { + name: 'foo', + targetKey: key })); test.done(); @@ -46,15 +46,15 @@ export = { enabled: false }); - test.throws(() => new EncryptionKeyAlias(stack, 'Alias', { - alias: 'alias/', - key + test.throws(() => new Alias(stack, 'Alias', { + name: 'alias/', + targetKey: key })); test.done(); }, - 'fails if alias starts with "alias/AWS"'(test: Test) { + 'fails if alias contains illegal characters'(test: Test) { const app = new App(); const stack = new Stack(app, 'Test'); @@ -63,19 +63,36 @@ export = { enabled: false }); - test.throws(() => new EncryptionKeyAlias(stack, 'Alias', { - alias: 'alias/AWS', - key + test.throws(() => new Alias(stack, 'Alias', { + name: 'alias/@Nope', + targetKey: key + }), 'a-zA-Z0-9:/_-'); + + test.done(); + }, + + 'fails if alias starts with "alias/aws/"'(test: Test) { + const app = new App(); + const stack = new Stack(app, 'Test'); + + const key = new Key(stack, 'MyKey', { + enableKeyRotation: true, + enabled: false + }); + + test.throws(() => new Alias(stack, 'Alias', { + name: 'alias/aws/', + targetKey: key })); - test.throws(() => new EncryptionKeyAlias(stack, 'Alias', { - alias: 'alias/AWSAwesome', - key + test.throws(() => new Alias(stack, 'Alias', { + name: 'alias/aws/Awesome', + targetKey: key })); - test.throws(() => new EncryptionKeyAlias(stack, 'Alias', { - alias: 'alias/AWS/awesome', - key + test.throws(() => new Alias(stack, 'Alias', { + name: 'alias/AWS/awesome', + targetKey: key })); test.done();