From fe9be9f90899574c624ff77bf7d4ff14613090e2 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 25 Sep 2018 00:55:38 -0700 Subject: [PATCH] feat(ec2): Add tag support to security groups (#766) --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- ...g.asg-w-classic-loadbalancer.expected.json | 7 + .../test/integ.asg-w-elbv2.expected.json | 14 ++ .../test/test.auto-scaling-group.ts | 123 ++++++------------ .../@aws-cdk/aws-ec2/lib/security-group.ts | 16 ++- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 2 +- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 68 +++++----- .../test/integ.elb.expected.json | 1 + .../test/integ.alb.expected.json | 1 + .../test/integ.vpc-lambda.expected.json | 1 + .../aws-rds/test/integ.cluster.expected.json | 56 +++++++- .../@aws-cdk/aws-rds/test/integ.cluster.ts | 3 + packages/@aws-cdk/cdk/lib/core/tag-manager.ts | 8 +- 13 files changed, 182 insertions(+), 120 deletions(-) diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index dff13ce13b862..8d55ff8a5582f 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -492,7 +492,7 @@ function renderRollingUpdateConfig(config: RollingUpdateConfiguration = {}): cdk class TagManager extends cdk.TagManager { protected tagFormatResolve(tagGroups: cdk.TagGroups): any { - const tags = {...tagGroups.nonSitckyTags, ...tagGroups.ancestorTags, ...tagGroups.stickyTags}; + const tags = {...tagGroups.nonStickyTags, ...tagGroups.ancestorTags, ...tagGroups.stickyTags}; return Object.keys(tags).map( (key) => { const propagateAtLaunch = !!tagGroups.propagateTags[key] || !!tagGroups.ancestorTags[key]; return {key, value: tags[key], propagateAtLaunch}; diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json index 8a854a4d8df7f..11f6f40f5d196 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json @@ -453,6 +453,12 @@ } ], "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "VpcId": { "Ref": "VPCB9E5F0B4" } @@ -583,6 +589,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json index 7e8084da884f9..e7f59cf158a9a 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json @@ -319,6 +319,12 @@ } ], "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "VpcId": { "Ref": "VPCB9E5F0B4" } @@ -405,6 +411,13 @@ "LaunchConfigurationName": { "Ref": "FleetLaunchConfig59F79D36" }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-cdk-ec2-integ/Fleet" + } + ], "TargetGroupARNs": [ { "Ref": "LBListenerTargetGroupF04FCF6D" @@ -463,6 +476,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 58d8f9b699971..c7c31f3558712 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -19,28 +19,35 @@ export = { expect(stack).toMatch({ "Resources": { - "MyFleetInstanceSecurityGroup774E8234": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "MyFleet/InstanceSecurityGroup", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Outbound traffic allowed by default", - "FromPort": -1, - "IpProtocol": "-1", - "ToPort": -1 + "MyFleetInstanceSecurityGroup774E8234": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "MyFleet/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Outbound traffic allowed by default", + "FromPort": -1, + "IpProtocol": "-1", + "ToPort": -1 + } + ], + "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "MyFleet" + } + ], + + "VpcId": "my-vpc" } - ], - "SecurityGroupIngress": [], - "VpcId": "my-vpc" - } - }, - "MyFleetInstanceRole25A84AB8": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ + }, + "MyFleetInstanceRole25A84AB8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", @@ -99,19 +106,19 @@ export = { "LaunchConfigurationName": { "Ref": "MyFleetLaunchConfig5D7F9801" }, - "LoadBalancerNames": [], - "Tags": [ - { - "Key": "Name", - "PropagateAtLaunch": true, - "Value": "MyFleet" - } - ], - "MaxSize": "1", - "MinSize": "1", - "VPCZoneIdentifier": [ - "pri1" - ] + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "MyFleet" + } + ], + + "MaxSize": "1", + "MinSize": "1", + "VPCZoneIdentifier": [ + "pri1" + ] } } } @@ -180,54 +187,6 @@ export = { test.done(); }, - 'can configure rolling update'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }}); - const vpc = mockVpc(stack); - - // WHEN - new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - { - "Ref": "MyFleetInstanceRole25A84AB8" - } - ], - Version: "2012-10-17" - }, - })); - test.done(); - }, - - 'can configure replacing update'(test: Test) { - // GIVEN - const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }}); - const vpc = mockVpc(stack); - - // WHEN - new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), - machineImage: new ec2.AmazonLinuxImage(), - vpc, - updateType: autoscaling.UpdateType.ReplacingUpdate, - replacingUpdateMinSuccessfulInstancesPercent: 50 - }); - - // THEN - expect(stack).to(haveResource("AWS::AutoScaling::AutoScalingGroup", { - UpdatePolicy: { - AutoScalingReplacingUpdate: { - WillReplace: true - } - }, - CreationPolicy: { - AutoScalingCreationPolicy: { - MinSuccessfulInstancesPercent: 50 - } - } - }, ResourcePart.CompleteDefinition)); - - test.done(); - }, - 'can configure rolling update'(test: Test) { // GIVEN const stack = new cdk.Stack(undefined, 'MyStack', { env: { region: 'us-east-1', account: '1234' }}); diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index b4df710d1eda5..4f48e59b74991 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -1,4 +1,4 @@ -import { Construct, Output, Token } from '@aws-cdk/cdk'; +import { Construct, ITaggable, Output, TagManager, Tags, Token } from '@aws-cdk/cdk'; import { Connections, IConnectable } from './connections'; import { cloudformation } from './ec2.generated'; import { IPortRange, ISecurityGroupRule } from './security-group-rule'; @@ -106,6 +106,11 @@ export interface SecurityGroupProps { */ description?: string; + /** + * The AWS resource tags to associate with the security group. + */ + tags?: Tags; + /** * The VPC in which to create the security group. */ @@ -119,7 +124,7 @@ export interface SecurityGroupProps { * inline ingress and egress rule (which saves on the total number of resources inside * the template). */ -export class SecurityGroup extends SecurityGroupRef { +export class SecurityGroup extends SecurityGroupRef implements ITaggable { /** * An attribute that represents the security group name. */ @@ -135,6 +140,11 @@ export class SecurityGroup extends SecurityGroupRef { */ public readonly securityGroupId: string; + /** + * Manage tags for this construct and children + */ + public readonly tags: TagManager; + private readonly securityGroup: cloudformation.SecurityGroupResource; private readonly directIngressRules: cloudformation.SecurityGroupResource.IngressProperty[] = []; private readonly directEgressRules: cloudformation.SecurityGroupResource.EgressProperty[] = []; @@ -142,6 +152,7 @@ export class SecurityGroup extends SecurityGroupRef { constructor(parent: Construct, name: string, props: SecurityGroupProps) { super(parent, name); + this.tags = new TagManager(this, { initialTags: props.tags}); const groupDescription = props.description || this.path; this.securityGroup = new cloudformation.SecurityGroupResource(this, 'Resource', { groupName: props.groupName, @@ -149,6 +160,7 @@ export class SecurityGroup extends SecurityGroupRef { securityGroupIngress: new Token(() => this.directIngressRules), securityGroupEgress: new Token(() => this.directEgressRules), vpcId: props.vpc.vpcId, + tags: this.tags, }); this.securityGroupId = this.securityGroup.securityGroupId; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index b6ba29a9df281..faf8c4b7f0a63 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -468,7 +468,7 @@ export class VpcSubnet extends VpcSubnetRef implements cdk.ITaggable { constructor(parent: cdk.Construct, name: string, props: VpcSubnetProps) { super(parent, name); - this.tags = new cdk.TagManager(this, props.tags); + this.tags = new cdk.TagManager(this, {initialTags: props.tags}); this.tags.setTag(NAME_TAG, this.path, {overwrite: false}); this.availabilityZone = props.availabilityZone; diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 459ab504462a5..b9a52dfad6d50 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -128,6 +128,10 @@ export = { cidrMask: 24, name: 'ingress', subnetType: SubnetType.Public, + tags: { + type: 'Public', + init: 'No', + }, }, { cidrMask: 24, @@ -155,44 +159,50 @@ export = { CidrBlock: `10.0.6.${i * 16}/28` })); } + expect(stack).to(haveResource("AWS::EC2::Subnet", hasTags( + [ + { Key: 'type', Value: 'Public'}, + { Key: 'init', Value: 'No'}, + ], + ))); test.done(); }, "with custom subents and natGateways = 2 there should be only two NATGW"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { - cidr: '10.0.0.0/21', - natGateways: 2, - subnetConfiguration: [ - { - cidrMask: 24, - name: 'ingress', - subnetType: SubnetType.Public, - }, - { - cidrMask: 24, - name: 'application', - subnetType: SubnetType.Private, - }, - { - cidrMask: 28, - name: 'rds', - subnetType: SubnetType.Isolated, - } - ], - maxAZs: 3 + cidr: '10.0.0.0/21', + natGateways: 2, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.Public, + }, + { + cidrMask: 24, + name: 'application', + subnetType: SubnetType.Private, + }, + { + cidrMask: 28, + name: 'rds', + subnetType: SubnetType.Isolated, + } + ], + maxAZs: 3 }); expect(stack).to(countResources("AWS::EC2::InternetGateway", 1)); expect(stack).to(countResources("AWS::EC2::NatGateway", 2)); expect(stack).to(countResources("AWS::EC2::Subnet", 9)); for (let i = 0; i < 6; i++) { - expect(stack).to(haveResource("AWS::EC2::Subnet", { - CidrBlock: `10.0.${i}.0/24` - })); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); } for (let i = 0; i < 3; i++) { - expect(stack).to(haveResource("AWS::EC2::Subnet", { - CidrBlock: `10.0.6.${i * 16}/28` - })); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.6.${i * 16}/28` + })); } test.done(); }, @@ -229,9 +239,9 @@ export = { expect(stack).to(countResources("AWS::EC2::Subnet", 4)); expect(stack).to(countResources("AWS::EC2::Route", 4)); for (let i = 0; i < 4; i++) { - expect(stack).to(haveResource("AWS::EC2::Subnet", { - CidrBlock: `10.0.${i * 64}.0/18` - })); + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i * 64}.0/18` + })); } expect(stack).to(haveResource("AWS::EC2::Route", { DestinationCidrBlock: '0.0.0.0/0', diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json index 4b05529da2869..0203dcbaccd44 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.expected.json @@ -185,6 +185,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json index 07eeab7b440b4..5d337cebcb360 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json @@ -343,6 +343,7 @@ "ToPort": 80 } ], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json index 396988864f19f..93e729c8fe111 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.vpc-lambda.expected.json @@ -382,6 +382,7 @@ } ], "SecurityGroupIngress": [], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json index c4e75bcba0011..0da9a7177f3b8 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json @@ -315,6 +315,53 @@ } } }, + "DbSecurity381C2C15": { + "Type": "AWS::KMS::Key", + "Properties": { + "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" + }, "DatabaseSubnets56F17B9A": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { @@ -335,6 +382,7 @@ "GroupDescription": "RDS security group", "SecurityGroupEgress": [], "SecurityGroupIngress": [], + "Tags": [], "VpcId": { "Ref": "VPCB9E5F0B4" } @@ -376,9 +424,15 @@ "DBSubnetGroupName": { "Ref": "DatabaseSubnets56F17B9A" }, + "KmsKeyId": { + "Fn::GetAtt": [ + "DbSecurity381C2C15", + "Arn" + ] + }, "MasterUsername": "admin", "MasterUserPassword": "7959866cacc02c2d243ecfe177464fe6", - "StorageEncrypted": false, + "StorageEncrypted": true, "VpcSecurityGroupIds": [ { "Fn::GetAtt": [ diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index a5d21a14e6b63..d59dc9913fea3 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -1,4 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); import { DatabaseCluster, DatabaseClusterEngine } from '../lib'; import { ClusterParameterGroup } from '../lib/cluster-parameter-group'; @@ -14,6 +15,7 @@ const params = new ClusterParameterGroup(stack, 'Params', { }); params.setParameter('character_set_database', 'utf8mb4'); +const kmsKey = new kms.EncryptionKey(stack, 'DbSecurity'); const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.Aurora, masterUser: { @@ -26,6 +28,7 @@ const cluster = new DatabaseCluster(stack, 'Database', { vpc }, parameterGroup: params, + kmsKeyArn: kmsKey.keyArn, }); cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/cdk/lib/core/tag-manager.ts b/packages/@aws-cdk/cdk/lib/core/tag-manager.ts index d0b4b5daa00cd..d8763158cedd6 100644 --- a/packages/@aws-cdk/cdk/lib/core/tag-manager.ts +++ b/packages/@aws-cdk/cdk/lib/core/tag-manager.ts @@ -60,7 +60,7 @@ export interface TagGroups { /** * Tags that are overwritten by ancestor tags */ - nonSitckyTags: Tags; + nonStickyTags: Tags; /** * Tags with propagate true not from an ancestor @@ -209,14 +209,14 @@ export class TagManager extends Token { return parentTags; } - const nonSitckyTags = filterTags(this._tags, {sticky: false}); + const nonStickyTags = filterTags(this._tags, {sticky: false}); const stickyTags = filterTags(this._tags, {sticky: true}); const ancestors = this.parent.ancestors(); const ancestorTags = propagatedTags(ancestors); const propagateTags = filterTags(this._tags, {propagate: true}); return this.tagFormatResolve( { ancestorTags, - nonSitckyTags, + nonStickyTags, stickyTags, propagateTags, }); @@ -263,7 +263,7 @@ export class TagManager extends Token { * additional CloudFormation key for `PropagateAtLaunch` */ protected tagFormatResolve(tagGroups: TagGroups): any { - const tags = {...tagGroups.nonSitckyTags, ...tagGroups.ancestorTags, ...tagGroups.stickyTags}; + const tags = {...tagGroups.nonStickyTags, ...tagGroups.ancestorTags, ...tagGroups.stickyTags}; for (const key of this.blockedTags) { delete tags[key]; } return Object.keys(tags).map( key => ({key, value: tags[key]})); }