diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index d7155f5f49f29..4a9d1963fdd92 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import logs = require('@aws-cdk/aws-logs'); -import { CfnOutput, Construct, HashedAddressingScheme, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, HashedAddressingScheme, IResource, Resource } from '@aws-cdk/cdk'; import { CfnStream } from './kinesis.generated'; export interface IStream extends IResource, logs.ILogSubscriptionDestination { @@ -22,12 +22,7 @@ export interface IStream extends IResource, logs.ILogSubscriptionDestination { /** * Optional KMS encryption key associated with this stream. */ - readonly encryptionKey?: kms.IEncryptionKey; - - /** - * Exports this stream from the stack. - */ - export(): StreamAttributes; + readonly encryptionKey?: kms.IKey; /** * Grant read permissions for this stream and its contents to an IAM @@ -71,7 +66,7 @@ export interface StreamAttributes { /** * The KMS key securing the contents of the stream if encryption is enabled. */ - readonly encryptionKey?: kms.EncryptionKeyImportProps; + readonly encryptionKey?: kms.IKey; } /** @@ -105,15 +100,13 @@ abstract class StreamBase extends Resource implements IStream { /** * Optional KMS encryption key associated with this stream. */ - public abstract readonly encryptionKey?: kms.IEncryptionKey; + public abstract readonly encryptionKey?: kms.IKey; /** * The role that can be used by CloudWatch logs to write to this stream */ private cloudWatchLogsRole?: iam.Role; - public abstract export(): StreamAttributes; - /** * Grant write permissions for this stream and its contents to an IAM * principal (Role/Group/User). @@ -278,7 +271,7 @@ export interface StreamProps { * @default If encryption is set to "Kms" and this property is undefined, a * new KMS key will be created and associated with this stream. */ - readonly encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IKey; } /** @@ -298,17 +291,10 @@ export class Stream extends StreamBase { * @param attrs Stream import properties */ public static fromStreamAttributes(scope: Construct, id: string, attrs: StreamAttributes): IStream { - const encryptionKey = attrs.encryptionKey - ? kms.EncryptionKey.import(scope, 'Key', attrs.encryptionKey) - : undefined; - class Import extends StreamBase { public readonly streamArn = attrs.streamArn; public readonly streamName = scope.node.stack.parseArn(attrs.streamArn).resourceName!; - public readonly encryptionKey = encryptionKey; - public export() { - return attrs; - } + public readonly encryptionKey = attrs.encryptionKey; } return new Import(scope, id); @@ -316,7 +302,7 @@ export class Stream extends StreamBase { public readonly streamArn: string; public readonly streamName: string; - public readonly encryptionKey?: kms.IEncryptionKey; + public readonly encryptionKey?: kms.IKey; private readonly stream: CfnStream; @@ -344,23 +330,13 @@ export class Stream extends StreamBase { if (props.streamName) { this.node.addMetadata('aws:cdk:hasPhysicalName', props.streamName); } } - /** - * Exports this stream from the stack. - */ - public export(): StreamAttributes { - return { - streamArn: new CfnOutput(this, 'StreamArn', { value: this.streamArn }).makeImportValue().toString(), - encryptionKey: this.encryptionKey ? this.encryptionKey.export() : undefined, - }; - } - /** * Set up key properties and return the Stream encryption property from the * user's configuration. */ private parseEncryption(props: StreamProps): { streamEncryption?: CfnStream.StreamEncryptionProperty, - encryptionKey?: kms.IEncryptionKey + encryptionKey?: kms.IKey } { // default to unencrypted. @@ -376,7 +352,7 @@ export class Stream extends StreamBase { } if (encryptionType === StreamEncryption.Kms) { - const encryptionKey = props.encryptionKey || new kms.EncryptionKey(this, 'Key', { + const encryptionKey = props.encryptionKey || new kms.Key(this, 'Key', { description: `Created by ${this.node.path}` }); diff --git a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts index eb0de8d1dc7f4..a8f857ef5f5d6 100644 --- a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts +++ b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts @@ -1,7 +1,7 @@ import { expect } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import cdk = require('@aws-cdk/cdk'); +import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Stream, StreamEncryption } from '../lib'; @@ -9,7 +9,7 @@ import { Stream, StreamEncryption } from '../lib'; export = { 'default stream'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); new Stream(stack, 'MyStream'); @@ -28,7 +28,7 @@ export = { test.done(); }, "uses explicit shard count"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); new Stream(stack, 'MyStream', { shardCount: 2 @@ -44,12 +44,12 @@ export = { } } } - }); + }); test.done(); }, "uses explicit retention period"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); new Stream(stack, 'MyStream', { retentionPeriodHours: 168 @@ -72,7 +72,7 @@ export = { "retention period must be between 24 and 168 hours"(test: Test) { test.throws({ block: () => { - new Stream(new cdk.Stack(), 'MyStream', { + new Stream(new Stack(), 'MyStream', { retentionPeriodHours: 169 }); }, @@ -81,7 +81,7 @@ export = { test.throws({ block: () => { - new Stream(new cdk.Stack(), 'MyStream', { + new Stream(new Stack(), 'MyStream', { retentionPeriodHours: 23 }); }, @@ -91,7 +91,7 @@ export = { test.done(); }, "auto-creates KMS key if encryption type is KMS but no key is provided"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); new Stream(stack, 'MyStream', { encryption: StreamEncryption.Kms @@ -106,40 +106,40 @@ export = { "KeyPolicy": { "Statement": [ { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] ] - ] - } - }, - "Resource": "*" + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -169,9 +169,9 @@ export = { test.done(); }, "uses explicit KMS key if encryption type is KMS and a key is provided"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); - const explicitKey = new kms.EncryptionKey(stack, 'ExplicitKey', { + const explicitKey = new kms.Key(stack, 'ExplicitKey', { description: `Explicit Key` }); @@ -189,40 +189,40 @@ export = { "KeyPolicy": { "Statement": [ { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] ] - ] - } - }, - "Resource": "*" + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -254,7 +254,7 @@ export = { "permissions": { "with encryption": { "grantRead creates and attaches a policy with read only access to Stream and EncryptionKey"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const stream = new Stream(stack, 'MyStream', { encryption: StreamEncryption.Kms }); @@ -358,8 +358,8 @@ export = { "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "MyStream5C050E93", - "Arn" + "MyStream5C050E93", + "Arn" ] } }, @@ -390,7 +390,7 @@ export = { test.done(); }, "grantWrite creates and attaches a policy with write only access to Stream and EncryptionKey"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const stream = new Stream(stack, 'MyStream', { encryption: StreamEncryption.Kms }); @@ -534,7 +534,7 @@ export = { test.done(); }, "grantReadWrite creates and attaches a policy with access to Stream and EncryptionKey"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const stream = new Stream(stack, 'MyStream', { encryption: StreamEncryption.Kms }); @@ -684,7 +684,7 @@ export = { }, "with no encryption": { "grantRead creates and associates a policy with read only access to Stream"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const stream = new Stream(stack, 'MyStream'); const user = new iam.User(stack, "MyUser"); @@ -738,7 +738,7 @@ export = { test.done(); }, "grantWrite creates and attaches a policy with write only access to Stream"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const stream = new Stream(stack, 'MyStream'); const user = new iam.User(stack, "MyUser"); @@ -792,7 +792,7 @@ export = { test.done(); }, "greatReadWrite creates and attaches a policy with write only access to Stream"(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const stream = new Stream(stack, 'MyStream'); const user = new iam.User(stack, "MyUser"); @@ -851,14 +851,12 @@ export = { }, "cross-stack permissions": { "no encryption"(test: Test) { - const stackA = new cdk.Stack(); + const stackA = new Stack(); const streamFromStackA = new Stream(stackA, 'MyStream'); - const refToStreamFromStackA = streamFromStackA.export(); - const stackB = new cdk.Stack(); + const stackB = new Stack(); const user = new iam.User(stackB, 'UserWhoNeedsAccess'); - const theStreamFromStackAAsARefInStackB = Stream.fromStreamAttributes(stackB, 'RefToStreamFromStackA', refToStreamFromStackA); - theStreamFromStackAAsARefInStackB.grantRead(user); + streamFromStackA.grantRead(user); expect(stackA).toMatch({ "Resources": { @@ -869,161 +867,6 @@ export = { "ShardCount": 1 } } - }, - "Outputs": { - "MyStreamStreamArn495BAFC1": { - "Value": { - "Fn::GetAtt": [ - "MyStream5C050E93", - "Arn" - ] - }, - "Export": { - "Name": "Stack:MyStreamStreamArn495BAFC1" - } - } - } - }); - - expect(stackB).toMatch({ - "Resources": { - "UserWhoNeedsAccessF8959C3D": { - "Type": "AWS::IAM::User" - }, - "UserWhoNeedsAccessDefaultPolicy6A9EB530": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStream", - "kinesis:GetRecords", - "kinesis:GetShardIterator" - ], - "Effect": "Allow", - "Resource": { - "Fn::ImportValue": "Stack:MyStreamStreamArn495BAFC1" - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "UserWhoNeedsAccessDefaultPolicy6A9EB530", - "Users": [ - { - "Ref": "UserWhoNeedsAccessF8959C3D" - } - ] - } - } - } - }); - - test.done(); - }, - "with encryption"(test: Test) { - const stackA = new cdk.Stack(); - const streamFromStackA = new Stream(stackA, 'MyStream', { - encryption: StreamEncryption.Kms - }); - const refToStreamFromStackA = streamFromStackA.export(); - - const stackB = new cdk.Stack(); - const user = new iam.User(stackB, 'UserWhoNeedsAccess'); - const theStreamFromStackAAsARefInStackB = Stream.fromStreamAttributes(stackB, 'RefToStreamFromStackA', refToStreamFromStackA); - theStreamFromStackAAsARefInStackB.grantRead(user); - - expect(stackA).toMatch({ - "Resources": { - "MyStreamKey76F3300E": { - "Type": "AWS::KMS::Key", - "Properties": { - "Description": "Created by MyStream", - "KeyPolicy": { - "Statement": [ - { - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::", - { - "Ref": "AWS::AccountId" - }, - ":root" - ] - ] - } - }, - "Resource": "*" - } - ], - "Version": "2012-10-17" - } - }, - "DeletionPolicy": "Retain" - }, - "MyStream5C050E93": { - "Type": "AWS::Kinesis::Stream", - "Properties": { - "RetentionPeriodHours": 24, - "ShardCount": 1, - "StreamEncryption": { - "EncryptionType": "KMS", - "KeyId": { - "Fn::GetAtt": [ - "MyStreamKey76F3300E", - "Arn" - ] - } - } - } - } - }, - "Outputs": { - "MyStreamKeyKeyArn967BCB03": { - "Value": { - "Fn::GetAtt": [ - "MyStreamKey76F3300E", - "Arn" - ] - }, - "Export": { - "Name": "Stack:MyStreamKeyKeyArn967BCB03" - } - }, - "MyStreamStreamArn495BAFC1": { - "Value": { - "Fn::GetAtt": [ - "MyStream5C050E93", - "Arn" - ] - }, - "Export": { - "Name": "Stack:MyStreamStreamArn495BAFC1" - } - } } }); @@ -1045,14 +888,7 @@ export = { ], "Effect": "Allow", "Resource": { - "Fn::ImportValue": "Stack:MyStreamStreamArn495BAFC1" - } - }, - { - "Action": "kms:Decrypt", - "Effect": "Allow", - "Resource": { - "Fn::ImportValue": "Stack:MyStreamKeyKeyArn967BCB03" + "Fn::ImportValue": "Stack:ExportsOutputFnGetAttMyStream5C050E93Arn4ABF30CD" } } ], @@ -1069,6 +905,20 @@ export = { } }); + test.done(); + }, + "fails with encryption due to cyclic dependency"(test: Test) { + const app = new App(); + const stackA = new Stack(app, 'stackA'); + const streamFromStackA = new Stream(stackA, 'MyStream', { + encryption: StreamEncryption.Kms + }); + + const stackB = new Stack(app, 'stackB'); + const user = new iam.User(stackB, 'UserWhoNeedsAccess'); + streamFromStackA.grantRead(user); + + test.throws(() => app.run(), /'stackB' depends on 'stackA'/); test.done(); } }