From 9052eb65471f942236f0739417c0ed4944ced204 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 26 Mar 2019 22:36:56 +0100 Subject: [PATCH 01/22] feat(ec2): add support for vpc endpoints Add support for both gateway and interface VPC endpoints. Static members are exposed for all AWS service endpoints. As gateway endpoints reference route tables, they currently cannot be added to imported VPC networks. --- packages/@aws-cdk/aws-ec2/README.md | 5 + packages/@aws-cdk/aws-ec2/lib/index.ts | 1 + packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 425 ++++++++++++ packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 25 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 48 ++ packages/@aws-cdk/aws-ec2/package.json | 3 +- .../test/integ.vpc-endpoint.lit.expected.json | 650 ++++++++++++++++++ .../aws-ec2/test/integ.vpc-endpoint.lit.ts | 36 + .../aws-ec2/test/test.vpc-endpoint.ts | 366 ++++++++++ 9 files changed, 1556 insertions(+), 3 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts create mode 100644 packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index f5b310ddad0e7..e660d6afd8ae1 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -353,3 +353,8 @@ const vpnConnection = vpc.addVpnConnection('Dynamic', { }); const state = vpnConnection.metricTunnelState(); ``` + +### VPC endpoints +VPC gateway and interface endpoints can be added to a VPC: + +[example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts) diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index fdfe81aa9b97a..32946b75d470c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -7,6 +7,7 @@ export * from './vpc'; export * from './vpc-ref'; export * from './vpc-network-provider'; export * from './vpn'; +export * from './vpc-endpoint'; // AWS::EC2 CloudFormation Resources: export * from './ec2.generated'; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts new file mode 100644 index 0000000000000..cd0c07bce8a55 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -0,0 +1,425 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import { Connections, IConnectable } from './connections'; +import { CfnVPCEndpoint } from './ec2.generated'; +import { SecurityGroup } from './security-group'; +import { VpcSubnet } from './vpc'; +import { IVpcNetwork, IVpcSubnet, SubnetSelection } from './vpc-ref'; + +/** + * A VPC endpoint. + */ +export interface IVpcEndpoint extends cdk.IConstruct { + /** + * The VPC endpoint identifier. + */ + readonly vpcEndpointId: string; +} + +/** + * A VPC gateway endpoint. + */ +export interface IVpcGatewayEndpoint extends IVpcEndpoint { + /** + * Exports this VPC endpoint from the stack. + */ + export(): VpcGatewayEndpointImportProps; +} + +/** + * The type of VPC endpoint. + */ +export enum VpcEndpointType { + /** + * Interface + */ + Interface = 'Interface', + + /** + * Gateway + */ + Gateway = 'Gateway' +} + +/** + * A VPC endpoint service. + */ +export class VpcEndpointService { + constructor(public readonly name: string, public readonly type: VpcEndpointType) {} +} + +/** + * A VPC endpoint AWS service. + */ +export class VpcEndpointAwsService extends VpcEndpointService { + public static readonly SageMakeNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); + public static readonly CloudFormation = new VpcEndpointAwsService('cloudformation'); + public static readonly CloudTrail = new VpcEndpointAwsService('cloudtrail'); + public static readonly CodeBuild = new VpcEndpointAwsService('codebuild'); + public static readonly CodeBuildFips = new VpcEndpointAwsService('codebuil-fips'); + public static readonly CodeCommit = new VpcEndpointAwsService('codecommit'); + public static readonly CodeCommitFips = new VpcEndpointAwsService('codecommit-fips'); + public static readonly CodePipeline = new VpcEndpointAwsService('codepipeline'); + public static readonly Config = new VpcEndpointAwsService('config'); + public static readonly DynamoDb = new VpcEndpointAwsService('dynamodb', VpcEndpointType.Gateway); + public static readonly Ec2 = new VpcEndpointAwsService('ec2'); + public static readonly Ec2Messages = new VpcEndpointAwsService('ec2messages'); + public static readonly Ecr = new VpcEndpointAwsService('ecr.api'); + public static readonly EcrDocker = new VpcEndpointAwsService('ecr.dkr'); + public static readonly Ecs = new VpcEndpointAwsService('ecs'); + public static readonly EcsAgent = new VpcEndpointAwsService('ecs-agent'); + public static readonly EcsTelemetry = new VpcEndpointAwsService('ecs-telemetry'); + public static readonly ElasticInferenceRuntime = new VpcEndpointAwsService('elastic-inference.runtime'); + public static readonly ElasticLoadBalancing = new VpcEndpointAwsService('elasticloadbalancing'); + public static readonly CloudWatchEvents = new VpcEndpointAwsService('events'); + public static readonly ApiGateway = new VpcEndpointAwsService('execute-api'); + public static readonly CodeCommitGit = new VpcEndpointAwsService('git-codecommit'); + public static readonly CodeCommitGitFips = new VpcEndpointAwsService('git-codecommit-fips'); + public static readonly KinesisStreams = new VpcEndpointAwsService('kinesis-streams'); + public static readonly Kms = new VpcEndpointAwsService('kms'); + public static readonly CloudWatchLogs = new VpcEndpointAwsService('logs'); + public static readonly CloudWatch = new VpcEndpointAwsService('monitoring'); + public static readonly S3 = new VpcEndpointAwsService('s3', VpcEndpointType.Gateway); + public static readonly SageMakerApi = new VpcEndpointAwsService('sagemaker.api'); + public static readonly SageMakerRuntime = new VpcEndpointAwsService('sagemaker.runtime'); + public static readonly SageMakerRuntimeFips = new VpcEndpointAwsService('sagemaker.runtime-fips'); + public static readonly SecretsManager = new VpcEndpointAwsService('secretsmanager'); + public static readonly ServiceCatalog = new VpcEndpointAwsService('servicecatalog'); + public static readonly Sns = new VpcEndpointAwsService('sns'); + public static readonly Sqs = new VpcEndpointAwsService('sqs'); + public static readonly Ssm = new VpcEndpointAwsService('ssm'); + public static readonly SsmMessages = new VpcEndpointAwsService('ssmmessages'); + public static readonly Sts = new VpcEndpointAwsService('sts'); + public static readonly Transfer = new VpcEndpointAwsService('transfer.server'); + + constructor(name: string, type?: VpcEndpointType, prefix?: string) { + super(`${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`, type || VpcEndpointType.Interface); + } +} + +/** + * Options to add an endpoint to a VPC. + */ +export interface VpcEndpointOptions { + /** + * The name of the service. + */ + service: VpcEndpointService; +} + +/** + * Options to add a gateway endpoint to a VPC. + */ +export interface VpcGatewayEndpointOptions extends VpcEndpointOptions { + /** + * Where to add endpoint routing. + * + * @default private subnets + */ + subnets?: SubnetSelection[] +} + +/** + * Construction properties for a GatewayEndpoint. + */ +export interface VpcGatewayEndpointProps extends VpcGatewayEndpointOptions { + /** + * The VPC network in which the gateway endpoint will be used. + */ + vpc: IVpcNetwork +} + +/** + * A VPC gateway endpoint. + */ +export class VpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndpoint { + /** + * Imports an existing gateway endpoint. + */ + public static import(scope: cdk.Construct, id: string, props: VpcGatewayEndpointImportProps): IVpcGatewayEndpoint { + return new ImportedVpcGatewayEndpoint(scope, id, props); + } + + /** + * The VPC gateway endpoint identifier. + */ + public readonly vpcEndpointId: string; + + /** + * The date and time the VPC gateway endpoint was created. + */ + public readonly creationTimestamp: string; + + private policyDocument?: iam.PolicyDocument; + + constructor(scope: cdk.Construct, id: string, props: VpcGatewayEndpointProps) { + super(scope, id); + + if (props.service.type !== VpcEndpointType.Gateway) { + throw new Error('The service endpoint type must be `Gateway`'); + } + + let subnets: IVpcSubnet[] = []; + if (props.subnets) { + for (const selection of props.subnets) { + subnets.push(...props.vpc.subnets(selection)); + } + } else { + subnets = props.vpc.subnets(); + } + + const routeTableIds = (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId); + + const endpoint = new CfnVPCEndpoint(this, 'Resource', { + policyDocument: new cdk.Token(() => this.policyDocument), + routeTableIds, + serviceName: props.service.name, + vpcEndpointType: VpcEndpointType.Gateway, + vpcId: props.vpc.vpcId + }); + + this.vpcEndpointId = endpoint.vpcEndpointId; + this.creationTimestamp = endpoint.vpcEndpointCreationTimestamp; + } + + /** + * Adds a statement to the policy document. + * + * @param statement the statement to add + */ + public addToPolicy(statement: iam.PolicyStatement) { + if (!statement.hasPrincipal) { + throw new Error('Statement must have a `Principal`.'); + } + + if (!this.policyDocument) { + this.policyDocument = new iam.PolicyDocument(); + } + + this.policyDocument.addStatement(statement); + } + + /** + * Exports this VPC gateway endpoint from the stack. + */ + public export(): VpcGatewayEndpointImportProps { + return { + vpcEndpointId: new cdk.CfnOutput(this, 'VpcEndpointId', { value: this.vpcEndpointId }).makeImportValue().toString() + }; + } +} + +/** + * Construction properties for an ImportedVpcGatewayEndpoint. + */ +export interface VpcGatewayEndpointImportProps { + /** + * The VPC gateway endpoint identifier. + */ + vpcEndpointId: string; +} + +/** + * An imported VPC gateway endpoint. + */ +class ImportedVpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndpoint { + /** + * The VPC gateway endpoint identifier. + */ + public readonly vpcEndpointId: string; + + constructor(scope: cdk.Construct, id: string, private readonly props: VpcGatewayEndpointImportProps) { + super(scope, id); + + this.vpcEndpointId = props.vpcEndpointId; + } + + /** + * Exports this VPC gateway endpoint from the stack. + */ + public export(): VpcGatewayEndpointImportProps { + return this.props; + } +} + +/** + * Options to add an interface endpoint to a VPC. + */ +export interface VpcInterfaceEndpointOptions extends VpcEndpointOptions { + /** + * Whether to associate a private hosted zone with the specified VPC. This + * allows you to make requests to the service using its default DNS hostname. + * + * @default true + */ + privateDnsEnabled?: boolean; + + /** + * The subnets in which to create an endpoint network interface. One per + * availability zone. + */ + subnets?: SubnetSelection; +} + +/** + * Construction properties for a VpcInterfaceEndpoint. + */ +export interface VpcInterfaceEndpointProps extends VpcInterfaceEndpointOptions { + /** + * The VPC network in which the endpoint will be used. + */ + vpc: IVpcNetwork +} + +/** + * A VPC interface endpoint. + */ +export interface IVpcInterfaceEndpoint extends IVpcEndpoint, IConnectable { + /** + * Exports this VPC interface endpoint from the stack. + */ + export(): VpcInterfaceEndpointImportProps; +} + +/** + * A VPC interface endpoint. + */ +export class VpcInterfaceEndpoint extends cdk.Construct implements IVpcInterfaceEndpoint { + /** + * Imports an existing interface endpoint. + */ + public static import(scope: cdk.Construct, id: string, props: VpcInterfaceEndpointImportProps): IVpcInterfaceEndpoint { + return new ImportedVpcInterfaceEndpoint(scope, id, props); + } + + /** + * The VPC interface endpoint identifier. + */ + public readonly vpcEndpointId: string; + + /** + * The date and time the VPC interface endpoint was created. + */ + public readonly creationTimestamp: string; + + /** + * The DNS entries for the VPC interface endpoint. + */ + public readonly dnsEntries: string[]; + + /** + * One or more network interfaces for the VPC interface endpoint. + */ + public readonly networkInterfaceIds: string[]; + + /** + * The identifier of the security group associated with this VPC interface + * endpoint. + */ + public readonly securityGroupId: string; + + /** + * Access to network connections. + */ + public readonly connections: Connections; + + constructor(scope: cdk.Construct, id: string, props: VpcInterfaceEndpointProps) { + super(scope, id); + + if (props.service.type !== VpcEndpointType.Interface) { + throw new Error('The service endpoint type must be `Interface`'); + } + + const securityGroup = new SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc + }); + this.securityGroupId = securityGroup.securityGroupId; + this.connections = new Connections({ securityGroups: [securityGroup] }); + + const subnets = props.vpc.subnets(props.subnets); + + const availabilityZones = new Set(subnets.map(s => s.availabilityZone)); + + if (availabilityZones.size !== subnets.length) { + throw new Error('Only one subnet per availability zone is allowed.'); + } + + const endpoint = new CfnVPCEndpoint(this, 'Resource', { + privateDnsEnabled: props.privateDnsEnabled || true, + securityGroupIds: [this.securityGroupId], + serviceName: props.service.name, + vpcEndpointType: VpcEndpointType.Interface, + subnetIds: subnets.map(s => s.subnetId), + vpcId: props.vpc.vpcId + }); + + this.vpcEndpointId = endpoint.vpcEndpointId; + this.creationTimestamp = endpoint.vpcEndpointCreationTimestamp; + this.dnsEntries = endpoint.vpcEndpointDnsEntries; + this.networkInterfaceIds = endpoint.vpcEndpointNetworkInterfaceIds; + } + + /** + * Exports this VPC interface endpoint from the stack. + */ + public export(): VpcInterfaceEndpointImportProps { + return { + vpcEndpointId: new cdk.CfnOutput(this, 'VpcEndpointId', { value: this.vpcEndpointId }).makeImportValue().toString(), + securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId }).makeImportValue().toString() + }; + } +} + +/** + * Construction properties for an ImportedVpcInterfaceEndpoint. + */ +export interface VpcInterfaceEndpointImportProps { + /** + * The VPC interface endpoint identifier. + */ + vpcEndpointId: string; + + /** + * The identifier of the security group associated with the VPC interface endpoint. + */ + securityGroupId: string; +} + +/** + * An imported VPC interface endpoint. + */ +class ImportedVpcInterfaceEndpoint extends cdk.Construct implements IVpcInterfaceEndpoint { + /** + * The VPC interface endpoint identifier. + */ + public readonly vpcEndpointId: string; + + /** + * The identifier of the security group associated with the VPC interface endpoint. + */ + public readonly securityGroupId: string; + + /** + * Access to network connections. + */ + public readonly connections: Connections; + + constructor(scope: cdk.Construct, id: string, private readonly props: VpcInterfaceEndpointImportProps) { + super(scope, id); + + this.vpcEndpointId = props.vpcEndpointId; + + this.securityGroupId = props.securityGroupId; + + this.connections = new Connections({ + securityGroups: [SecurityGroup.import(this, 'SecurityGroup', props)], + }); + } + + /** + * Exports this VPC endpoint from the stack. + */ + public export(): VpcInterfaceEndpointImportProps { + return this.props; + } +} diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 25b1eb46daeab..0d46d6ea59269 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,5 +1,6 @@ import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk"; import { DEFAULT_SUBNET_NAME, subnetName } from './util'; +import { VpcInterfaceEndpoint, VpcInterfaceEndpointOptions } from './vpc-endpoint'; import { VpnConnection, VpnConnectionOptions } from './vpn'; export interface IVpcSubnet extends IConstruct { @@ -71,6 +72,11 @@ export interface IVpcNetwork extends IConstruct { */ subnetIds(selection?: SubnetSelection): string[]; + /** + * Return the subnets appropriate for the placement strategy + */ + subnets(selection?: SubnetSelection): IVpcSubnet[]; + /** * Return a dependable object representing internet connectivity for the given subnets */ @@ -86,6 +92,11 @@ export interface IVpcNetwork extends IConstruct { */ addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection; + /** + * Adds a new interface endpoint to this VPC + */ + addInterfaceEndpoint(id: string, options: VpcInterfaceEndpointOptions): VpcInterfaceEndpoint + /** * Exports this VPC so it can be consumed by another stack. */ @@ -243,6 +254,16 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { }); } + /** + * Adds a new interface endpoint to this VPC + */ + public addInterfaceEndpoint(id: string, options: VpcInterfaceEndpointOptions): VpcInterfaceEndpoint { + return new VpcInterfaceEndpoint(this, id, { + vpc: this, + ...options + }); + } + /** * Export this VPC from the stack */ @@ -266,7 +287,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Return the subnets appropriate for the placement strategy */ - protected subnets(selection: SubnetSelection = {}): IVpcSubnet[] { + public subnets(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); // Select by name @@ -415,4 +436,4 @@ class CompositeDependable implements IDependable { } return ret; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 241d9b276d80f..a80c88942d476 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -4,6 +4,7 @@ import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, Cfn import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated'; import { NetworkBuilder } from './network-util'; import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId } from './util'; +import { VpcEndpointAwsService, VpcGatewayEndpoint, VpcGatewayEndpointOptions } from './vpc-endpoint'; import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider'; import { IVpcNetwork, IVpcSubnet, SubnetSelection, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcSubnetImportProps } from './vpc-ref'; import { VpnConnectionOptions, VpnConnectionType } from './vpn'; @@ -144,6 +145,11 @@ export interface VpcNetworkProps { * @default on the route tables associated with private subnets */ vpnRoutePropagation?: SubnetType[] + + /** + * Gateway endpoints to add to this VPC. + */ + gatewayEndpoints?: { [id: string]: VpcGatewayEndpointOptions } } /** @@ -422,6 +428,48 @@ export class VpcNetwork extends VpcNetworkBase { this.addVpnConnection(connectionId, connection); } } + + // Allow creation of gateway endpoints on VPC instantiation as those can be + // immediately functional without further configuration. This is not the case + // for interface endpoints where the security group must be configured. + if (props.gatewayEndpoints) { + const gatewayEndpoints = props.gatewayEndpoints || {}; + for (const [endpointId, endpoint] of Object.entries(gatewayEndpoints)) { + this.addGatewayEndpoint(endpointId, endpoint); + } + } + } + + /** + * Adds a new gateway endpoint to this VPC + */ + public addGatewayEndpoint(id: string, options: VpcGatewayEndpointOptions): VpcGatewayEndpoint { + return new VpcGatewayEndpoint(this, id, { + vpc: this, + ...options + }); + } + + /** + * Adds a new S3 gateway endpoint to this VPC + */ + public addS3Endpoint(id: string, subnets?: SubnetSelection[]): VpcGatewayEndpoint { + return new VpcGatewayEndpoint(this, id, { + service: VpcEndpointAwsService.S3, + vpc: this, + subnets + }); + } + + /** + * Adds a new DynamoDB gateway endpoint to this VPC + */ + public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection[]): VpcGatewayEndpoint { + return new VpcGatewayEndpoint(this, id, { + service: VpcEndpointAwsService.DynamoDb, + vpc: this, + subnets + }); } /** diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 1943efc853c03..51690fb37e568 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -69,6 +69,7 @@ "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { "@aws-cdk/aws-cloudwatch": "^0.26.0", + "@aws-cdk/aws-iam": "^0.26.0", "@aws-cdk/cdk": "^0.26.0", "@aws-cdk/cx-api": "^0.26.0" }, @@ -80,4 +81,4 @@ "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json new file mode 100644 index 0000000000000..e0a19805633f5 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json @@ -0,0 +1,650 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc" + } + ] + } + }, + "MyVpcPublicSubnet1SubnetF6608456": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableC46AB2F4": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet1EIP096967CB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet2Subnet492B6BFB": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTable1DF17386": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTableAssociation227DE78D": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet2EIP8CCBA239": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet3Subnet57EEE236": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVpcPublicSubnet3RouteTable15028F08": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPublicSubnet3RouteTableAssociation5C27DDA4": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + } + } + }, + "MyVpcPublicSubnet3DefaultRoute3A83AB36": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet3EIPC5ACADAB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet3NATGatewayD4B50EBE": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet3EIPC5ACADAB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet1Subnet5057CF7E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "MyVpcPrivateSubnet2Subnet0040C983": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableCEDCEECE": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableAssociation86A610DA": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + } + }, + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, + "MyVpcPrivateSubnet3Subnet772D6AD7": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVpcPrivateSubnet3RouteTableB790927C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet3RouteTableAssociationD951741C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet3Subnet772D6AD7" + } + } + }, + "MyVpcPrivateSubnet3DefaultRouteEC11C0C5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet3NATGatewayD4B50EBE" + } + } + }, + "MyVpcIGW5C4A4F63": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "MyVpcS3FADC1889": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".s3" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "RouteTableIds": [ + { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + } + ], + "VpcEndpointType": "Gateway" + } + }, + "MyVpcDynamoDbEndpointE6A39B0D": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".dynamodb" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:DescribeTable", + "dynamodb:ListTables" + ], + "Effect": "Allow", + "Principal": "*", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "RouteTableIds": [ + { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + } + ], + "VpcEndpointType": "Gateway" + } + }, + "MyVpcEcrDockerEndpointSecurityGroup47BB9CC1": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-ec2-vpc-endpoint/MyVpc/EcrDockerEndpoint/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "from 0.0.0.0/0:443", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc" + } + ], + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + } + } + }, + "MyVpcEcrDockerEndpoint0385050C": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".ecr.dkr" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "MyVpcEcrDockerEndpointSecurityGroup47BB9CC1", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + }, + { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + }, + { + "Ref": "MyVpcPrivateSubnet3Subnet772D6AD7" + } + ], + "VpcEndpointType": "Interface" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts new file mode 100644 index 0000000000000..4612ac656127f --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts @@ -0,0 +1,36 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import ec2 = require('../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-ec2-vpc-endpoint'); + +/// !show +const vpc = new ec2.VpcNetwork(stack, 'MyVpc', { + gatewayEndpoints: { + S3: { + service: ec2.VpcEndpointAwsService.S3 + } + } +}); + +const dynamoDbEndpoint = vpc.addGatewayEndpoint('DynamoDbEndpoint', { + service: ec2.VpcEndpointAwsService.DynamoDb +}); + +// Restrict to listing and describing tables +dynamoDbEndpoint.addToPolicy( + new iam.PolicyStatement() + .addAnyPrincipal() + .addActions('dynamodb:DescribeTable', 'dynamodb:ListTables') + .addAllResources() +); + +const ecrDockerEndpoint = vpc.addInterfaceEndpoint('EcrDockerEndpoint', { + service: ec2.VpcEndpointAwsService.EcrDocker +}); + +ecrDockerEndpoint.connections.allowFromAnyIPv4(new ec2.TcpPort(443)); +/// !hide + +app.run(); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts new file mode 100644 index 0000000000000..3376062bd0c2d --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -0,0 +1,366 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { PolicyStatement } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import { SubnetType, TcpPort, VpcEndpointAwsService, VpcGatewayEndpoint, VpcInterfaceEndpoint, VpcNetwork } from '../lib'; + +export = { + 'gateway endpoint': { + 'add an endpoint to a vpc'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new VpcNetwork(stack, 'VpcNetwork', { + gatewayEndpoints: { + S3: { + service: VpcEndpointAwsService.S3 + } + } + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.s3' + ] + ] + }, + VpcId: { + Ref: 'VpcNetworkB258E83A' + }, + RouteTableIds: [ + { + Ref: 'VpcNetworkPrivateSubnet1RouteTableCD085FF1' + }, + { + Ref: 'VpcNetworkPrivateSubnet2RouteTableE97B328B' + }, + { + Ref: 'VpcNetworkPrivateSubnet3RouteTableE0C661A2' + } + ], + VpcEndpointType: 'Gateway' + })); + + test.done(); + }, + + 'throws when creating with a bad endpoint type'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + + // THEN + test.throws(() => vpc.addGatewayEndpoint('Bad', { + service: VpcEndpointAwsService.Ec2 + }), /`Gateway`/); + + test.done(); + }, + + 'routing on private and public subnets'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new VpcNetwork(stack, 'VpcNetwork', { + gatewayEndpoints: { + S3: { + service: VpcEndpointAwsService.S3, + subnets: [ + { subnetType: SubnetType.Public }, + { subnetType: SubnetType.Private } + ] + } + } + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.s3' + ] + ] + }, + VpcId: { + Ref: 'VpcNetworkB258E83A' + }, + RouteTableIds: [ + { + Ref: 'VpcNetworkPublicSubnet1RouteTable25CCC53F' + }, + { + Ref: 'VpcNetworkPublicSubnet2RouteTableE5F348DF' + }, + { + Ref: 'VpcNetworkPublicSubnet3RouteTable36E30B07' + }, + { + Ref: 'VpcNetworkPrivateSubnet1RouteTableCD085FF1' + }, + { + Ref: 'VpcNetworkPrivateSubnet2RouteTableE97B328B' + }, + { + Ref: 'VpcNetworkPrivateSubnet3RouteTableE0C661A2' + } + ], + VpcEndpointType: 'Gateway' + })); + + test.done(); + }, + + 'add statements to policy'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + const endpoint = vpc.addGatewayEndpoint('S3', { + service: VpcEndpointAwsService.S3 + }); + + // WHEN + endpoint.addToPolicy( + new PolicyStatement() + .addAnyPrincipal() + .addActions('s3:GetObject', 's3:ListBucket') + .addAllResources() + ); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + PolicyDocument: { + Statement: [ + { + Action: [ + 's3:GetObject', + 's3:ListBucket' + ], + Effect: 'Allow', + Principal: '*', + Resource: '*' + } + ], + Version: '2012-10-17' + } + })); + + test.done(); + }, + + 'throws when adding a statement without a principal'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + const endpoint = vpc.addGatewayEndpoint('S3', { + service: VpcEndpointAwsService.S3 + }); + + // THEN + test.throws(() => endpoint.addToPolicy( + new PolicyStatement() + .addActions('s3:GetObject', 's3:ListBucket') + .addAllResources() + ), /`Principal`/); + + test.done(); + }, + + 'import/export'(test: Test) { + // GIVEN + const stack1 = new Stack(); + const stack2 = new Stack(); + const vpc = new VpcNetwork(stack1, 'Vpc1'); + const endpoint = vpc.addGatewayEndpoint('DynamoDB', { + service: VpcEndpointAwsService.DynamoDb + }); + + // WHEN + VpcGatewayEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); + + // THEN: No error + test.done(); + }, + + 'conveniance methods for S3 and DynamoDB'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + + // WHEN + vpc.addS3Endpoint('S3'); + vpc.addDynamoDbEndpoint('DynamoDb'); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.s3' + ] + ] + }, + })); + + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.dynamodb' + ] + ] + }, + })); + + test.done(); + } + }, + + 'interface endpoint': { + 'add an endpoint to a vpc'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + + // WHEN + vpc.addInterfaceEndpoint('EcrDocker', { + service: VpcEndpointAwsService.EcrDocker + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.ecr.dkr' + ] + ] + }, + VpcId: { + Ref: 'VpcNetworkB258E83A' + }, + PrivateDnsEnabled: true, + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'VpcNetworkEcrDockerSecurityGroup7C91D347', + 'GroupId' + ] + } + ], + SubnetIds: [ + { + Ref: 'VpcNetworkPrivateSubnet1Subnet07BA143B' + }, + { + Ref: 'VpcNetworkPrivateSubnet2Subnet5E4189D6' + }, + { + Ref: 'VpcNetworkPrivateSubnet3Subnet5D16E0FB' + } + ], + VpcEndpointType: 'Interface' + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'VpcNetwork/EcrDocker/SecurityGroup', + VpcId: { + Ref: 'VpcNetworkB258E83A' + } + })); + + test.done(); + }, + + 'throws when creating with a bad endpoint type'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + + // THEN + test.throws(() => vpc.addInterfaceEndpoint('Bad', { + service: VpcEndpointAwsService.S3 + }), /`Interface`/); + + test.done(); + }, + + 'throws when using more than one subnet per availability zone'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const vpc = new VpcNetwork(stack, 'VpcNetwork', { + maxAZs: 1, + subnetConfiguration: [ + {name: 'app', subnetType: SubnetType.Private }, + {name: 'db', subnetType: SubnetType.Private }, + ] + }); + + // THEN + test.throws(() => vpc.addInterfaceEndpoint('Bad', { + service: VpcEndpointAwsService.SecretsManager + }), /availability zone/); + + test.done(); + }, + + 'import/export'(test: Test) { + // GIVEN + const stack1 = new Stack(); + const stack2 = new Stack(); + const vpc = new VpcNetwork(stack1, 'Vpc1'); + const endpoint = vpc.addInterfaceEndpoint('EC2', { + service: VpcEndpointAwsService.Ec2 + }); + + // WHEN + const importedEndpoint = VpcInterfaceEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); + importedEndpoint.connections.allowFromAnyIPv4(new TcpPort(443)); + + // THEN + expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { + GroupId: { + 'Fn::ImportValue': 'Stack:Vpc1EC2SecurityGroupId3B169C3F' + } + })); + + test.done(); + } + } +}; From 746507e629fcae33ac0f8f152c91b5fe727f6b96 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 28 Mar 2019 09:23:21 +0100 Subject: [PATCH 02/22] Add readonly --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index cd0c07bce8a55..a46fd2f0cf3ad 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -104,7 +104,7 @@ export interface VpcEndpointOptions { /** * The name of the service. */ - service: VpcEndpointService; + readonly service: VpcEndpointService; } /** @@ -116,7 +116,7 @@ export interface VpcGatewayEndpointOptions extends VpcEndpointOptions { * * @default private subnets */ - subnets?: SubnetSelection[] + readonly subnets?: SubnetSelection[] } /** @@ -126,7 +126,7 @@ export interface VpcGatewayEndpointProps extends VpcGatewayEndpointOptions { /** * The VPC network in which the gateway endpoint will be used. */ - vpc: IVpcNetwork + readonly vpc: IVpcNetwork } /** @@ -216,7 +216,7 @@ export interface VpcGatewayEndpointImportProps { /** * The VPC gateway endpoint identifier. */ - vpcEndpointId: string; + readonly vpcEndpointId: string; } /** @@ -252,13 +252,13 @@ export interface VpcInterfaceEndpointOptions extends VpcEndpointOptions { * * @default true */ - privateDnsEnabled?: boolean; + readonly privateDnsEnabled?: boolean; /** * The subnets in which to create an endpoint network interface. One per * availability zone. */ - subnets?: SubnetSelection; + readonly subnets?: SubnetSelection; } /** @@ -268,7 +268,7 @@ export interface VpcInterfaceEndpointProps extends VpcInterfaceEndpointOptions { /** * The VPC network in which the endpoint will be used. */ - vpc: IVpcNetwork + readonly vpc: IVpcNetwork } /** @@ -377,12 +377,12 @@ export interface VpcInterfaceEndpointImportProps { /** * The VPC interface endpoint identifier. */ - vpcEndpointId: string; + readonly vpcEndpointId: string; /** * The identifier of the security group associated with the VPC interface endpoint. */ - securityGroupId: string; + readonly securityGroupId: string; } /** From 2a29faab86b6ee07d26cb42bab8cc569287fcd26 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 28 Mar 2019 11:42:19 +0100 Subject: [PATCH 03/22] Fix typo --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index a46fd2f0cf3ad..b0880761ca4fe 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -52,7 +52,7 @@ export class VpcEndpointService { * A VPC endpoint AWS service. */ export class VpcEndpointAwsService extends VpcEndpointService { - public static readonly SageMakeNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); + public static readonly SageMakerNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); public static readonly CloudFormation = new VpcEndpointAwsService('cloudformation'); public static readonly CloudTrail = new VpcEndpointAwsService('cloudtrail'); public static readonly CodeBuild = new VpcEndpointAwsService('codebuild'); From a8e827e735b16827aa4c3dad388a864b67f8f434 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 28 Mar 2019 11:59:56 +0100 Subject: [PATCH 04/22] Convert VpcEndpointService to interface --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index b0880761ca4fe..92565b05df575 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -44,14 +44,22 @@ export enum VpcEndpointType { /** * A VPC endpoint service. */ -export class VpcEndpointService { - constructor(public readonly name: string, public readonly type: VpcEndpointType) {} +export interface VpcEndpointService { + /** + * The name of the service. + */ + readonly name: string; + + /** + * The type of the service. + */ + readonly type: VpcEndpointType; } /** * A VPC endpoint AWS service. */ -export class VpcEndpointAwsService extends VpcEndpointService { +export class VpcEndpointAwsService implements VpcEndpointService { public static readonly SageMakerNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); public static readonly CloudFormation = new VpcEndpointAwsService('cloudformation'); public static readonly CloudTrail = new VpcEndpointAwsService('cloudtrail'); @@ -92,8 +100,19 @@ export class VpcEndpointAwsService extends VpcEndpointService { public static readonly Sts = new VpcEndpointAwsService('sts'); public static readonly Transfer = new VpcEndpointAwsService('transfer.server'); + /** + * The name of the service. + */ + public readonly name: string; + + /** + * The type of the service. + */ + public readonly type: VpcEndpointType; + constructor(name: string, type?: VpcEndpointType, prefix?: string) { - super(`${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`, type || VpcEndpointType.Interface); + this.name = `${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`; + this.type = type || VpcEndpointType.Interface; } } @@ -102,7 +121,7 @@ export class VpcEndpointAwsService extends VpcEndpointService { */ export interface VpcEndpointOptions { /** - * The name of the service. + * The service to use for this VPC endpoint. */ readonly service: VpcEndpointService; } From 65293e41c4cf67d7f656fbfe71d9d4c6e59b6390 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 28 Mar 2019 22:04:15 +0100 Subject: [PATCH 05/22] IVpcEndpointService, JSDoc, README and integ test --- packages/@aws-cdk/aws-ec2/README.md | 4 +- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 13 +++- .../aws-ec2/test/integ.vpc-endpoint.lit.ts | 61 +++++++++++-------- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index e660d6afd8ae1..eb68f3c9d79dd 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -355,6 +355,8 @@ const state = vpnConnection.metricTunnelState(); ``` ### VPC endpoints -VPC gateway and interface endpoints can be added to a VPC: +A VPC endpoint enables you to privately connect your VPC to supported AWS services and VPC endpoint services powered by PrivateLink without requiring an internet gateway, NAT device, VPN connection, or AWS Direct Connect connection. Instances in your VPC do not require public IP addresses to communicate with resources in the service. Traffic between your VPC and the other service does not leave the Amazon network. + +Endpoints are virtual devices. They are horizontally scaled, redundant, and highly available VPC components that allow communication between instances in your VPC and services without imposing availability risks or bandwidth constraints on your network traffic. [example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 92565b05df575..827bcc40c6c4e 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -32,11 +32,18 @@ export interface IVpcGatewayEndpoint extends IVpcEndpoint { export enum VpcEndpointType { /** * Interface + * + * An interface endpoint is an elastic network interface with a private IP + * address that serves as an entry point for traffic destined to a supported + * service. */ Interface = 'Interface', /** * Gateway + * + * A gateway endpoint is a gateway that is a target for a specified route in + * your route table, used for traffic destined to a supported AWS service. */ Gateway = 'Gateway' } @@ -44,7 +51,7 @@ export enum VpcEndpointType { /** * A VPC endpoint service. */ -export interface VpcEndpointService { +export interface IVpcEndpointService { /** * The name of the service. */ @@ -59,7 +66,7 @@ export interface VpcEndpointService { /** * A VPC endpoint AWS service. */ -export class VpcEndpointAwsService implements VpcEndpointService { +export class VpcEndpointAwsService implements IVpcEndpointService { public static readonly SageMakerNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); public static readonly CloudFormation = new VpcEndpointAwsService('cloudformation'); public static readonly CloudTrail = new VpcEndpointAwsService('cloudtrail'); @@ -123,7 +130,7 @@ export interface VpcEndpointOptions { /** * The service to use for this VPC endpoint. */ - readonly service: VpcEndpointService; + readonly service: IVpcEndpointService; } /** diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts index 4612ac656127f..feb2975db08fa 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts @@ -3,34 +3,45 @@ import cdk = require('@aws-cdk/cdk'); import ec2 = require('../lib'); const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-cdk-ec2-vpc-endpoint'); - -/// !show -const vpc = new ec2.VpcNetwork(stack, 'MyVpc', { - gatewayEndpoints: { - S3: { - service: ec2.VpcEndpointAwsService.S3 - } - } -}); -const dynamoDbEndpoint = vpc.addGatewayEndpoint('DynamoDbEndpoint', { - service: ec2.VpcEndpointAwsService.DynamoDb -}); +class VpcEndpointStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + /// !show + // Add gateway endpoints when creating the VPC + const vpc = new ec2.VpcNetwork(this, 'MyVpc', { + gatewayEndpoints: { + S3: { + service: ec2.VpcEndpointAwsService.S3 + } + } + }); -// Restrict to listing and describing tables -dynamoDbEndpoint.addToPolicy( - new iam.PolicyStatement() - .addAnyPrincipal() - .addActions('dynamodb:DescribeTable', 'dynamodb:ListTables') - .addAllResources() -); + // Alternatively gateway endpoints can be added on the VPC + const dynamoDbEndpoint = vpc.addGatewayEndpoint('DynamoDbEndpoint', { + service: ec2.VpcEndpointAwsService.DynamoDb + }); -const ecrDockerEndpoint = vpc.addInterfaceEndpoint('EcrDockerEndpoint', { - service: ec2.VpcEndpointAwsService.EcrDocker -}); + // This allows to customize the endpoint policy + dynamoDbEndpoint.addToPolicy( + new iam.PolicyStatement() // Restrict to listing and describing tables + .addAnyPrincipal() + .addActions('dynamodb:DescribeTable', 'dynamodb:ListTables') + .addAllResources() + ); -ecrDockerEndpoint.connections.allowFromAnyIPv4(new ec2.TcpPort(443)); -/// !hide + // Add an interface endpoint + const ecrDockerEndpoint = vpc.addInterfaceEndpoint('EcrDockerEndpoint', { + service: ec2.VpcEndpointAwsService.EcrDocker + }); + + // When working with an interface endpoint, use the connections object to + // allow traffic to flow to the endpoint. + ecrDockerEndpoint.connections.allowFromAnyIPv4(new ec2.TcpPort(443)); + /// !hide + } +} +new VpcEndpointStack(app, 'aws-cdk-ec2-vpc-endpoint'); app.run(); From c799be0ea183548bcea1fe00c00747ce427b9ee1 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 28 Mar 2019 23:03:45 +0100 Subject: [PATCH 06/22] Extend SubnetSelection BREAKING CHANGE: `SubnetSelection` now expects either a list of subnet types (`subnetTypes`) or a list of subnet names (`subnetNames`) BREAKING CHANGE: `vpnRoutePropagation` now expects a `SubnetSelection` BREAKING CHANGE: `vpcSubnets` in `ClusterProps` in `@aws-cdk/aws-eks` now expects a `SubnetSelection` --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- .../test/test.auto-scaling-group.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 17 ++--- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 65 +++++++++++-------- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 20 ++---- .../aws-ec2/test/test.vpc-endpoint.ts | 7 +- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 29 +++++---- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- .../aws-ecs/test/ec2/test.ec2-service.ts | 4 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 16 ++--- .../test/example.ssh-into-nodes.lit.ts | 2 +- .../lib/shared/base-load-balancer.ts | 2 +- .../aws-lambda/test/test.vpc-lambda.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 4 +- .../@aws-cdk/aws-rds/test/integ.cluster.ts | 2 +- 15 files changed, 81 insertions(+), 95 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 c8429013018a7..b0003e9abc368 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -292,7 +292,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup }; if (!props.vpc.isPublicSubnets(subnetIds) && props.associatePublicIpAddress) { - throw new Error("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.Public })"); + throw new Error("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetTypes: [SubnetType.Public] })"); } this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps); 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 f833fe4eec5b7..de3a07de76cd5 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 @@ -419,7 +419,7 @@ export = { maxCapacity: 0, desiredCapacity: 0, - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] }, associatePublicIpAddress: true, }); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 827bcc40c6c4e..b413e86498957 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -4,7 +4,7 @@ import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; import { SecurityGroup } from './security-group'; import { VpcSubnet } from './vpc'; -import { IVpcNetwork, IVpcSubnet, SubnetSelection } from './vpc-ref'; +import { IVpcNetwork, SubnetSelection } from './vpc-ref'; /** * A VPC endpoint. @@ -142,7 +142,7 @@ export interface VpcGatewayEndpointOptions extends VpcEndpointOptions { * * @default private subnets */ - readonly subnets?: SubnetSelection[] + readonly subnets?: SubnetSelection } /** @@ -185,15 +185,7 @@ export class VpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndp throw new Error('The service endpoint type must be `Gateway`'); } - let subnets: IVpcSubnet[] = []; - if (props.subnets) { - for (const selection of props.subnets) { - subnets.push(...props.vpc.subnets(selection)); - } - } else { - subnets = props.vpc.subnets(); - } - + const subnets = props.vpc.selectSubnets(props.subnets); const routeTableIds = (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId); const endpoint = new CfnVPCEndpoint(this, 'Resource', { @@ -362,8 +354,7 @@ export class VpcInterfaceEndpoint extends cdk.Construct implements IVpcInterface this.securityGroupId = securityGroup.securityGroupId; this.connections = new Connections({ securityGroups: [securityGroup] }); - const subnets = props.vpc.subnets(props.subnets); - + const subnets = props.vpc.selectSubnets(props.subnets); const availabilityZones = new Set(subnets.map(s => s.availabilityZone)); if (availabilityZones.size !== subnets.length) { diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index d2de485bd0a94..64599c0a1ce33 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -75,7 +75,7 @@ export interface IVpcNetwork extends IConstruct { /** * Return the subnets appropriate for the placement strategy */ - subnets(selection?: SubnetSelection): IVpcSubnet[]; + selectSubnets(selection?: SubnetSelection): IVpcSubnet[]; /** * Return a dependable object representing internet connectivity for the given subnets @@ -152,16 +152,16 @@ export enum SubnetType { */ export interface SubnetSelection { /** - * Place the instances in the subnets of the given type + * Place the instances in the subnets of the given types * * At most one of `subnetType` and `subnetName` can be supplied. * * @default SubnetType.Private */ - readonly subnetType?: SubnetType; + readonly subnetTypes?: SubnetType[]; /** - * Place the instances in the subnets with the given name + * Place the instances in the subnets with the given names * * (This is the name supplied in subnetConfiguration). * @@ -169,7 +169,7 @@ export interface SubnetSelection { * * @default name */ - readonly subnetName?: string; + readonly subnetNames?: string[]; } /** @@ -223,7 +223,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { public subnetIds(selection: SubnetSelection = {}): string[] { selection = reifySelectionDefaults(selection); - const nets = this.subnets(selection); + const nets = this.selectSubnets(selection); if (nets.length === 0) { throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); } @@ -238,7 +238,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { selection = reifySelectionDefaults(selection); const ret = new CompositeDependable(); - for (const subnet of this.subnets(selection)) { + for (const subnet of this.selectSubnets(selection)) { ret.add(subnet.internetConnectivityEstablished); } return ret; @@ -287,27 +287,34 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Return the subnets appropriate for the placement strategy */ - public subnets(selection: SubnetSelection = {}): IVpcSubnet[] { + public selectSubnets(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); // Select by name - if (selection.subnetName !== undefined) { - const allSubnets = this.privateSubnets.concat(this.publicSubnets).concat(this.isolatedSubnets); - const selectedSubnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); - if (selectedSubnets.length === 0) { - throw new Error(`No subnets with name: ${selection.subnetName}`); + if (selection.subnetNames !== undefined) { + const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; + const names = selection.subnetNames; + const nameSubnets = allSubnets.filter(s => names.includes(subnetName(s))); + if (nameSubnets.length === 0) { + throw new Error(`No subnets with names in: ${selection.subnetNames}`); } - return selectedSubnets; + return nameSubnets; } // Select by type - if (selection.subnetType === undefined) { return this.privateSubnets; } + if (selection.subnetTypes === undefined) { return this.privateSubnets; } - return { - [SubnetType.Isolated]: this.isolatedSubnets, - [SubnetType.Private]: this.privateSubnets, - [SubnetType.Public]: this.publicSubnets, - }[selection.subnetType]; + let typeSubnets: IVpcSubnet[] = []; + if (selection.subnetTypes.includes(SubnetType.Public)) { + typeSubnets = [...typeSubnets, ...this.publicSubnets]; + } + if (selection.subnetTypes.includes(SubnetType.Private)) { + typeSubnets = [...typeSubnets, ...this.privateSubnets]; + } + if (selection.subnetTypes.includes(SubnetType.Isolated)) { + typeSubnets = [...typeSubnets, ...this.isolatedSubnets]; + } + return typeSubnets; } } @@ -392,12 +399,12 @@ export interface VpcSubnetImportProps { * Returns "private subnets" by default. */ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { - if (placement.subnetType !== undefined && placement.subnetName !== undefined) { + if (placement.subnetTypes !== undefined && placement.subnetNames !== undefined) { throw new Error('Only one of subnetType and subnetName can be supplied'); } - if (placement.subnetType === undefined && placement.subnetName === undefined) { - return { subnetType: SubnetType.Private }; + if (placement.subnetTypes === undefined && placement.subnetNames === undefined) { + return { subnetTypes: [SubnetType.Private] }; } return placement; @@ -407,15 +414,19 @@ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { * Describe the given placement strategy */ function describeSelection(placement: SubnetSelection): string { - if (placement.subnetType !== undefined) { - return `'${DEFAULT_SUBNET_NAME[placement.subnetType]}' subnets`; + if (placement.subnetTypes !== undefined) { + return `'${joinCommaOr(placement.subnetTypes.map(type => DEFAULT_SUBNET_NAME[type]))}' subnets`; } - if (placement.subnetName !== undefined) { - return `subnets named '${placement.subnetName}'`; + if (placement.subnetNames !== undefined) { + return `subnets named '${joinCommaOr(placement.subnetNames)}'`; } return JSON.stringify(placement); } +function joinCommaOr(array: string[]) { + return [array.slice(0, -1).join(', '), array.slice(-1)[0]].join(array.length < 2 ? '' : ' or '); +} + class CompositeDependable implements IDependable { private readonly dependables = new Array(); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 5b49092022f15..75c979ffe3328 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -144,7 +144,7 @@ export interface VpcNetworkProps { * * @default on the route tables associated with private subnets */ - readonly vpnRoutePropagation?: SubnetType[] + readonly vpnRoutePropagation?: SubnetSelection /** * Gateway endpoints to add to this VPC. @@ -402,17 +402,7 @@ export class VpcNetwork extends VpcNetworkBase { this.vpnGatewayId = vpnGateway.vpnGatewayName; // Propagate routes on route tables associated with the right subnets - const vpnRoutePropagation = props.vpnRoutePropagation || [SubnetType.Private]; - let subnets: IVpcSubnet[] = []; - if (vpnRoutePropagation.includes(SubnetType.Public)) { - subnets = [...subnets, ...this.publicSubnets]; - } - if (vpnRoutePropagation.includes(SubnetType.Private)) { - subnets = [...subnets, ...this.privateSubnets]; - } - if (vpnRoutePropagation.includes(SubnetType.Isolated)) { - subnets = [...subnets, ...this.isolatedSubnets]; - } + const subnets = this.selectSubnets(props.vpnRoutePropagation); const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { routeTableIds: (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId), vpnGatewayId: this.vpnGatewayId @@ -453,7 +443,7 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new S3 gateway endpoint to this VPC */ - public addS3Endpoint(id: string, subnets?: SubnetSelection[]): VpcGatewayEndpoint { + public addS3Endpoint(id: string, subnets?: SubnetSelection): VpcGatewayEndpoint { return new VpcGatewayEndpoint(this, id, { service: VpcEndpointAwsService.S3, vpc: this, @@ -464,7 +454,7 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new DynamoDB gateway endpoint to this VPC */ - public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection[]): VpcGatewayEndpoint { + public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection): VpcGatewayEndpoint { return new VpcGatewayEndpoint(this, id, { service: VpcEndpointAwsService.DynamoDb, vpc: this, @@ -502,7 +492,7 @@ export class VpcNetwork extends VpcNetworkBase { let natSubnets: VpcPublicSubnet[]; if (placement) { - const subnets = this.subnets(placement); + const subnets = this.selectSubnets(placement); for (const sub of subnets) { if (this.publicSubnets.indexOf(sub) === -1) { throw new Error(`natGatewayPlacement ${placement} contains non public subnet ${sub}`); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index 3376062bd0c2d..c537ad81e2747 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -77,10 +77,9 @@ export = { gatewayEndpoints: { S3: { service: VpcEndpointAwsService.S3, - subnets: [ - { subnetType: SubnetType.Public }, - { subnetType: SubnetType.Private } - ] + subnets: { + subnetTypes: [SubnetType.Public, SubnetType.Private] + } } } }); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 0a7dc1e4f1607..f310aff93ed97 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -115,7 +115,7 @@ export = { test.done(); }, - "with custom subents, the VPC should have the right number of subnets, an IGW, and a NAT Gateway per AZ"(test: Test) { + "with custom subnets, the VPC should have the right number of subnets, an IGW, and a NAT Gateway per AZ"(test: Test) { const stack = getTestStack(); const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; new VpcNetwork(stack, 'TheVPC', { @@ -154,7 +154,7 @@ export = { } test.done(); }, - "with custom subents and natGateways = 2 there should be only two NATGW"(test: Test) { + "with custom subnets and natGateways = 2 there should be only two NATGW"(test: Test) { const stack = getTestStack(); new VpcNetwork(stack, 'TheVPC', { cidr: '10.0.0.0/21', @@ -290,7 +290,7 @@ export = { }, ], natGatewaySubnets: { - subnetName: 'egress' + subnetNames: ['egress'] }, }); expect(stack).to(countResources("AWS::EC2::NatGateway", 3)); @@ -318,7 +318,7 @@ export = { }, ], natGatewaySubnets: { - subnetName: 'notthere', + subnetNames: ['notthere'], }, })); test.done(); @@ -371,7 +371,9 @@ export = { { subnetType: SubnetType.Isolated, name: 'Isolated' }, ], vpnGateway: true, - vpnRoutePropagation: [SubnetType.Isolated] + vpnRoutePropagation: { + subnetTypes: [SubnetType.Isolated] + } }); expect(stack).to(haveResource('AWS::EC2::VPNGatewayRoutePropagation', { @@ -401,10 +403,9 @@ export = { { subnetType: SubnetType.Isolated, name: 'Isolated' }, ], vpnGateway: true, - vpnRoutePropagation: [ - SubnetType.Private, - SubnetType.Isolated - ] + vpnRoutePropagation: { + subnetTypes: [SubnetType.Private, SubnetType.Isolated] + } }); expect(stack).to(haveResource('AWS::EC2::VPNGatewayRoutePropagation', { @@ -546,7 +547,7 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.subnetIds({ subnetType: SubnetType.Public }); + const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Public] }); // THEN test.deepEqual(nets, vpc.publicSubnets.map(s => s.subnetId)); @@ -565,7 +566,7 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetType: SubnetType.Isolated }); + const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); // THEN test.deepEqual(nets, vpc.isolatedSubnets.map(s => s.subnetId)); @@ -584,7 +585,7 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetName: 'DontTalkToMe' }); + const nets = vpc.subnetIds({ subnetNames: ['DontTalkToMe'] }); // THEN test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); @@ -665,7 +666,7 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetType: SubnetType.Isolated }); + const nets = importedVpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); @@ -688,7 +689,7 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetName: isolatedName }); + const nets = importedVpc.subnetIds({ subnetNames: [isolatedName] }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 062312d6cfb89..7ce18acf892c8 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -179,7 +179,7 @@ export abstract class BaseService extends cdk.Construct // tslint:disable-next-line:max-line-length protected configureAwsVpcNetworking(vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { if (vpcSubnets === undefined) { - vpcSubnets = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + vpcSubnets = { subnetTypes: assignPublicIp ? [ec2.SubnetType.Public] : [ec2.SubnetType.Private] }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index 45eb699e9785e..a6fc9e1009870 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -168,7 +168,7 @@ export = { cluster, taskDefinition, vpcSubnets: { - subnetType: ec2.SubnetType.Public + subnetTypes: [ec2.SubnetType.Public] } }); }); @@ -249,7 +249,7 @@ export = { cluster, taskDefinition, vpcSubnets: { - subnetType: ec2.SubnetType.Public + subnetTypes: [ec2.SubnetType.Public] } }); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 174d3eff33509..10ef87c88e1a8 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -27,13 +27,13 @@ export interface ClusterProps { * * ```ts * vpcSubnets: [ - * { subnetType: ec2.SubnetType.Private } + * { subnetTypes: [ec2.SubnetType.Private] } * ] * ``` * * @default All public and private subnets */ - readonly vpcSubnets?: ec2.SubnetSelection[]; + readonly vpcSubnets?: ec2.SubnetSelection; /** * Role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf. @@ -162,8 +162,8 @@ export class Cluster extends ClusterBase { }); // Get subnetIds for all selected subnets - const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.Public }, { subnetType: ec2.SubnetType.Private }]; - const subnetIds = flatMap(placements, p => this.vpc.subnetIds(p)); + const placements = props.vpcSubnets || { subnetTypes: [ec2.SubnetType.Public, ec2.SubnetType.Private] }; + const subnetIds = this.vpc.subnetIds(placements); const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, @@ -332,11 +332,3 @@ class ImportedCluster extends ClusterBase { } } } - -function flatMap(xs: T[], f: (x: T) => U[]): U[] { - const ret = new Array(); - for (const x of xs) { - ret.push(...f(x)); - } - return ret; -} diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index 5d188d352cadf..dddf1547be908 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -15,7 +15,7 @@ class EksClusterStack extends cdk.Stack { /// !show const asg = cluster.addCapacity('Nodes', { instanceType: new ec2.InstanceType('t2.medium'), - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] }, keyName: 'my-key-name', }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 2d2791a1a6f9f..789cfb45a286d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -99,7 +99,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const internetFacing = ifUndefined(baseProps.internetFacing, false); const vpcSubnets = ifUndefined(baseProps.vpcSubnets, - { subnetType: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private }); + { subnetTypes: internetFacing ? [ec2.SubnetType.Public] : [ec2.SubnetType.Private] }); const subnets = baseProps.vpc.subnetIds(vpcSubnets); diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts index b069c11a8d2cc..63a4790a00d7d 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts @@ -137,7 +137,7 @@ export = { handler: 'index.handler', runtime: lambda.Runtime.NodeJS610, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.Public } + vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] } }); }); diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 24d547b0262c1..c31e11e3cee64 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -312,7 +312,9 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu props.clusterIdentifier != null ? `${props.clusterIdentifier}instance${instanceIndex}` : undefined; - const publiclyAccessible = props.instanceProps.vpcSubnets && props.instanceProps.vpcSubnets.subnetType === ec2.SubnetType.Public; + const publiclyAccessible = props.instanceProps.vpcSubnets && + props.instanceProps.vpcSubnets.subnetTypes && + props.instanceProps.vpcSubnets.subnetTypes.includes(ec2.SubnetType.Public); const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index 281f22ed4e89b..acd01aad770e9 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -24,7 +24,7 @@ const cluster = new DatabaseCluster(stack, 'Database', { }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] }, vpc }, parameterGroup: params, From 625c5f4c9c8727ef3bd0eb03aef5e9de6ea3b832 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 29 Mar 2019 10:45:30 +0100 Subject: [PATCH 07/22] Default port range and naming --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 295 ++++++++++-------- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 8 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 20 +- .../aws-ec2/test/integ.vpc-endpoint.lit.ts | 8 +- .../aws-ec2/test/test.vpc-endpoint.ts | 55 +--- 5 files changed, 195 insertions(+), 191 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index b413e86498957..745931286fccb 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -3,6 +3,7 @@ import cdk = require('@aws-cdk/cdk'); import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; import { SecurityGroup } from './security-group'; +import { TcpPort, TcpPortFromAttribute } from './security-group-rule'; import { VpcSubnet } from './vpc'; import { IVpcNetwork, SubnetSelection } from './vpc-ref'; @@ -17,13 +18,13 @@ export interface IVpcEndpoint extends cdk.IConstruct { } /** - * A VPC gateway endpoint. + * A gateway VPC endpoint. */ -export interface IVpcGatewayEndpoint extends IVpcEndpoint { +export interface IGatewayVpcEndpoint extends IVpcEndpoint { /** * Exports this VPC endpoint from the stack. */ - export(): VpcGatewayEndpointImportProps; + export(): GatewayVpcEndpointImportProps; } /** @@ -49,94 +50,41 @@ export enum VpcEndpointType { } /** - * A VPC endpoint service. + * A service for a gateway VPC endpoint. */ -export interface IVpcEndpointService { +export interface IGatewayVpcEndpointService { /** * The name of the service. */ readonly name: string; - - /** - * The type of the service. - */ - readonly type: VpcEndpointType; } /** - * A VPC endpoint AWS service. + * An AWS service for a gateway VPC endpoint. */ -export class VpcEndpointAwsService implements IVpcEndpointService { - public static readonly SageMakerNotebook = new VpcEndpointAwsService('sagemaker', VpcEndpointType.Interface, 'aws.sagemaker'); - public static readonly CloudFormation = new VpcEndpointAwsService('cloudformation'); - public static readonly CloudTrail = new VpcEndpointAwsService('cloudtrail'); - public static readonly CodeBuild = new VpcEndpointAwsService('codebuild'); - public static readonly CodeBuildFips = new VpcEndpointAwsService('codebuil-fips'); - public static readonly CodeCommit = new VpcEndpointAwsService('codecommit'); - public static readonly CodeCommitFips = new VpcEndpointAwsService('codecommit-fips'); - public static readonly CodePipeline = new VpcEndpointAwsService('codepipeline'); - public static readonly Config = new VpcEndpointAwsService('config'); - public static readonly DynamoDb = new VpcEndpointAwsService('dynamodb', VpcEndpointType.Gateway); - public static readonly Ec2 = new VpcEndpointAwsService('ec2'); - public static readonly Ec2Messages = new VpcEndpointAwsService('ec2messages'); - public static readonly Ecr = new VpcEndpointAwsService('ecr.api'); - public static readonly EcrDocker = new VpcEndpointAwsService('ecr.dkr'); - public static readonly Ecs = new VpcEndpointAwsService('ecs'); - public static readonly EcsAgent = new VpcEndpointAwsService('ecs-agent'); - public static readonly EcsTelemetry = new VpcEndpointAwsService('ecs-telemetry'); - public static readonly ElasticInferenceRuntime = new VpcEndpointAwsService('elastic-inference.runtime'); - public static readonly ElasticLoadBalancing = new VpcEndpointAwsService('elasticloadbalancing'); - public static readonly CloudWatchEvents = new VpcEndpointAwsService('events'); - public static readonly ApiGateway = new VpcEndpointAwsService('execute-api'); - public static readonly CodeCommitGit = new VpcEndpointAwsService('git-codecommit'); - public static readonly CodeCommitGitFips = new VpcEndpointAwsService('git-codecommit-fips'); - public static readonly KinesisStreams = new VpcEndpointAwsService('kinesis-streams'); - public static readonly Kms = new VpcEndpointAwsService('kms'); - public static readonly CloudWatchLogs = new VpcEndpointAwsService('logs'); - public static readonly CloudWatch = new VpcEndpointAwsService('monitoring'); - public static readonly S3 = new VpcEndpointAwsService('s3', VpcEndpointType.Gateway); - public static readonly SageMakerApi = new VpcEndpointAwsService('sagemaker.api'); - public static readonly SageMakerRuntime = new VpcEndpointAwsService('sagemaker.runtime'); - public static readonly SageMakerRuntimeFips = new VpcEndpointAwsService('sagemaker.runtime-fips'); - public static readonly SecretsManager = new VpcEndpointAwsService('secretsmanager'); - public static readonly ServiceCatalog = new VpcEndpointAwsService('servicecatalog'); - public static readonly Sns = new VpcEndpointAwsService('sns'); - public static readonly Sqs = new VpcEndpointAwsService('sqs'); - public static readonly Ssm = new VpcEndpointAwsService('ssm'); - public static readonly SsmMessages = new VpcEndpointAwsService('ssmmessages'); - public static readonly Sts = new VpcEndpointAwsService('sts'); - public static readonly Transfer = new VpcEndpointAwsService('transfer.server'); +export class GatewayVpcEndpointAwsService implements IGatewayVpcEndpointService { + public static readonly DynamoDb = new GatewayVpcEndpointAwsService('dynamodb'); + public static readonly S3 = new GatewayVpcEndpointAwsService('s3'); /** * The name of the service. */ public readonly name: string; - /** - * The type of the service. - */ - public readonly type: VpcEndpointType; - - constructor(name: string, type?: VpcEndpointType, prefix?: string) { + constructor(name: string, prefix?: string) { this.name = `${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`; - this.type = type || VpcEndpointType.Interface; } } /** - * Options to add an endpoint to a VPC. + * Options to add a gateway endpoint to a VPC. */ -export interface VpcEndpointOptions { +export interface GatewayVpcEndpointOptions { /** - * The service to use for this VPC endpoint. + * The service to use for this gateway VPC endpoint. */ - readonly service: IVpcEndpointService; -} + readonly service: IGatewayVpcEndpointService; -/** - * Options to add a gateway endpoint to a VPC. - */ -export interface VpcGatewayEndpointOptions extends VpcEndpointOptions { /** * Where to add endpoint routing. * @@ -146,9 +94,9 @@ export interface VpcGatewayEndpointOptions extends VpcEndpointOptions { } /** - * Construction properties for a GatewayEndpoint. + * Construction properties for a GatewayVpcEndpoint. */ -export interface VpcGatewayEndpointProps extends VpcGatewayEndpointOptions { +export interface GatewayVpcEndpointProps extends GatewayVpcEndpointOptions { /** * The VPC network in which the gateway endpoint will be used. */ @@ -156,35 +104,31 @@ export interface VpcGatewayEndpointProps extends VpcGatewayEndpointOptions { } /** - * A VPC gateway endpoint. + * A gateway VPC endpoint. */ -export class VpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndpoint { +export class GatewayVpcEndpoint extends cdk.Construct implements IGatewayVpcEndpoint { /** - * Imports an existing gateway endpoint. + * Imports an existing gateway VPC endpoint. */ - public static import(scope: cdk.Construct, id: string, props: VpcGatewayEndpointImportProps): IVpcGatewayEndpoint { - return new ImportedVpcGatewayEndpoint(scope, id, props); + public static import(scope: cdk.Construct, id: string, props: GatewayVpcEndpointImportProps): IGatewayVpcEndpoint { + return new ImportedGatewayVpcEndpoint(scope, id, props); } /** - * The VPC gateway endpoint identifier. + * The gateway VPC endpoint identifier. */ public readonly vpcEndpointId: string; /** - * The date and time the VPC gateway endpoint was created. + * The date and time the gateway VPC endpoint was created. */ public readonly creationTimestamp: string; private policyDocument?: iam.PolicyDocument; - constructor(scope: cdk.Construct, id: string, props: VpcGatewayEndpointProps) { + constructor(scope: cdk.Construct, id: string, props: GatewayVpcEndpointProps) { super(scope, id); - if (props.service.type !== VpcEndpointType.Gateway) { - throw new Error('The service endpoint type must be `Gateway`'); - } - const subnets = props.vpc.selectSubnets(props.subnets); const routeTableIds = (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId); @@ -218,9 +162,9 @@ export class VpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndp } /** - * Exports this VPC gateway endpoint from the stack. + * Exports this gateway VPC endpoint from the stack. */ - public export(): VpcGatewayEndpointImportProps { + public export(): GatewayVpcEndpointImportProps { return { vpcEndpointId: new cdk.CfnOutput(this, 'VpcEndpointId', { value: this.vpcEndpointId }).makeImportValue().toString() }; @@ -228,42 +172,120 @@ export class VpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndp } /** - * Construction properties for an ImportedVpcGatewayEndpoint. + * Construction properties for an ImportedGatewayVpcEndpoint. */ -export interface VpcGatewayEndpointImportProps { +export interface GatewayVpcEndpointImportProps { /** - * The VPC gateway endpoint identifier. + * The gateway VPC endpoint identifier. */ readonly vpcEndpointId: string; } /** - * An imported VPC gateway endpoint. + * An imported gateway VPC endpoint. */ -class ImportedVpcGatewayEndpoint extends cdk.Construct implements IVpcGatewayEndpoint { +class ImportedGatewayVpcEndpoint extends cdk.Construct implements IGatewayVpcEndpoint { /** - * The VPC gateway endpoint identifier. + * The gateway VPC endpoint identifier. */ public readonly vpcEndpointId: string; - constructor(scope: cdk.Construct, id: string, private readonly props: VpcGatewayEndpointImportProps) { + constructor(scope: cdk.Construct, id: string, private readonly props: GatewayVpcEndpointImportProps) { super(scope, id); this.vpcEndpointId = props.vpcEndpointId; } /** - * Exports this VPC gateway endpoint from the stack. + * Exports this gateway VPC endpoint from the stack. */ - public export(): VpcGatewayEndpointImportProps { + public export(): GatewayVpcEndpointImportProps { return this.props; } } +/** + * A service for an interface VPC endpoint. + */ +export interface IInterfaceVpcEndpointService { + /** + * The name of the service. + */ + readonly name: string; + + /** + * The port of the service. + */ + readonly port: number; +} + +/** + * An AWS service for an interface VPC endpoint. + */ +export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointService { + public static readonly SageMakerNotebook = new InterfaceVpcEndpointAwsService('sagemaker', 'aws.sagemaker'); + public static readonly CloudFormation = new InterfaceVpcEndpointAwsService('cloudformation'); + public static readonly CloudTrail = new InterfaceVpcEndpointAwsService('cloudtrail'); + public static readonly CodeBuild = new InterfaceVpcEndpointAwsService('codebuild'); + public static readonly CodeBuildFips = new InterfaceVpcEndpointAwsService('codebuil-fips'); + public static readonly CodeCommit = new InterfaceVpcEndpointAwsService('codecommit'); + public static readonly CodeCommitFips = new InterfaceVpcEndpointAwsService('codecommit-fips'); + public static readonly CodePipeline = new InterfaceVpcEndpointAwsService('codepipeline'); + public static readonly Config = new InterfaceVpcEndpointAwsService('config'); + public static readonly Ec2 = new InterfaceVpcEndpointAwsService('ec2'); + public static readonly Ec2Messages = new InterfaceVpcEndpointAwsService('ec2messages'); + public static readonly Ecr = new InterfaceVpcEndpointAwsService('ecr.api'); + public static readonly EcrDocker = new InterfaceVpcEndpointAwsService('ecr.dkr'); + public static readonly Ecs = new InterfaceVpcEndpointAwsService('ecs'); + public static readonly EcsAgent = new InterfaceVpcEndpointAwsService('ecs-agent'); + public static readonly EcsTelemetry = new InterfaceVpcEndpointAwsService('ecs-telemetry'); + public static readonly ElasticInferenceRuntime = new InterfaceVpcEndpointAwsService('elastic-inference.runtime'); + public static readonly ElasticLoadBalancing = new InterfaceVpcEndpointAwsService('elasticloadbalancing'); + public static readonly CloudWatchEvents = new InterfaceVpcEndpointAwsService('events'); + public static readonly ApiGateway = new InterfaceVpcEndpointAwsService('execute-api'); + public static readonly CodeCommitGit = new InterfaceVpcEndpointAwsService('git-codecommit'); + public static readonly CodeCommitGitFips = new InterfaceVpcEndpointAwsService('git-codecommit-fips'); + public static readonly KinesisStreams = new InterfaceVpcEndpointAwsService('kinesis-streams'); + public static readonly Kms = new InterfaceVpcEndpointAwsService('kms'); + public static readonly CloudWatchLogs = new InterfaceVpcEndpointAwsService('logs'); + public static readonly CloudWatch = new InterfaceVpcEndpointAwsService('monitoring'); + public static readonly SageMakerApi = new InterfaceVpcEndpointAwsService('sagemaker.api'); + public static readonly SageMakerRuntime = new InterfaceVpcEndpointAwsService('sagemaker.runtime'); + public static readonly SageMakerRuntimeFips = new InterfaceVpcEndpointAwsService('sagemaker.runtime-fips'); + public static readonly SecretsManager = new InterfaceVpcEndpointAwsService('secretsmanager'); + public static readonly ServiceCatalog = new InterfaceVpcEndpointAwsService('servicecatalog'); + public static readonly Sns = new InterfaceVpcEndpointAwsService('sns'); + public static readonly Sqs = new InterfaceVpcEndpointAwsService('sqs'); + public static readonly Ssm = new InterfaceVpcEndpointAwsService('ssm'); + public static readonly SsmMessages = new InterfaceVpcEndpointAwsService('ssmmessages'); + public static readonly Sts = new InterfaceVpcEndpointAwsService('sts'); + public static readonly Transfer = new InterfaceVpcEndpointAwsService('transfer.server'); + + /** + * The name of the service. + */ + public readonly name: string; + + /** + * The port of the service. + */ + public readonly port: number; + + constructor(name: string, prefix?: string, port?: number) { + this.name = `${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`; + this.port = port || 443; + } +} + /** * Options to add an interface endpoint to a VPC. */ -export interface VpcInterfaceEndpointOptions extends VpcEndpointOptions { +export interface InterfaceVpcEndpointOptions { + /** + * The service to use for this interface VPC endpoint. + */ + readonly service: IInterfaceVpcEndpointService; + /** * Whether to associate a private hosted zone with the specified VPC. This * allows you to make requests to the service using its default DNS hostname. @@ -273,65 +295,67 @@ export interface VpcInterfaceEndpointOptions extends VpcEndpointOptions { readonly privateDnsEnabled?: boolean; /** - * The subnets in which to create an endpoint network interface. One per - * availability zone. + * The subnets in which to create an endpoint network interface. At most one + * per availability zone. + * + * @default private subnets */ readonly subnets?: SubnetSelection; } /** - * Construction properties for a VpcInterfaceEndpoint. + * Construction properties for an InterfaceVpcEndpoint. */ -export interface VpcInterfaceEndpointProps extends VpcInterfaceEndpointOptions { +export interface InterfaceVpcEndpointProps extends InterfaceVpcEndpointOptions { /** - * The VPC network in which the endpoint will be used. + * The VPC network in which the interface endpoint will be used. */ readonly vpc: IVpcNetwork } /** - * A VPC interface endpoint. + * An interface VPC endpoint. */ -export interface IVpcInterfaceEndpoint extends IVpcEndpoint, IConnectable { +export interface IInterfaceVpcEndpoint extends IVpcEndpoint, IConnectable { /** - * Exports this VPC interface endpoint from the stack. + * Exports this interface VPC endpoint from the stack. */ - export(): VpcInterfaceEndpointImportProps; + export(): InterfaceVpcEndpointImportProps; } /** - * A VPC interface endpoint. + * A interface VPC endpoint. */ -export class VpcInterfaceEndpoint extends cdk.Construct implements IVpcInterfaceEndpoint { +export class InterfaceVpcEndpoint extends cdk.Construct implements IInterfaceVpcEndpoint { /** - * Imports an existing interface endpoint. + * Imports an existing interface VPC endpoint. */ - public static import(scope: cdk.Construct, id: string, props: VpcInterfaceEndpointImportProps): IVpcInterfaceEndpoint { - return new ImportedVpcInterfaceEndpoint(scope, id, props); + public static import(scope: cdk.Construct, id: string, props: InterfaceVpcEndpointImportProps): IInterfaceVpcEndpoint { + return new ImportedInterfaceVpcEndpoint(scope, id, props); } /** - * The VPC interface endpoint identifier. + * The interface VPC endpoint identifier. */ public readonly vpcEndpointId: string; /** - * The date and time the VPC interface endpoint was created. + * The date and time the interface VPC endpoint was created. */ public readonly creationTimestamp: string; /** - * The DNS entries for the VPC interface endpoint. + * The DNS entries for the interface VPC endpoint. */ public readonly dnsEntries: string[]; /** - * One or more network interfaces for the VPC interface endpoint. + * One or more network interfaces for the interface VPC endpoint. */ public readonly networkInterfaceIds: string[]; /** - * The identifier of the security group associated with this VPC interface + * The identifier of the security group associated with this interface VPC * endpoint. */ public readonly securityGroupId: string; @@ -341,18 +365,20 @@ export class VpcInterfaceEndpoint extends cdk.Construct implements IVpcInterface */ public readonly connections: Connections; - constructor(scope: cdk.Construct, id: string, props: VpcInterfaceEndpointProps) { - super(scope, id); + private readonly port: number; - if (props.service.type !== VpcEndpointType.Interface) { - throw new Error('The service endpoint type must be `Interface`'); - } + constructor(scope: cdk.Construct, id: string, props: InterfaceVpcEndpointProps) { + super(scope, id); + this.port = props.service.port; const securityGroup = new SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc }); this.securityGroupId = securityGroup.securityGroupId; - this.connections = new Connections({ securityGroups: [securityGroup] }); + this.connections = new Connections({ + defaultPortRange: new TcpPort(props.service.port), + securityGroups: [securityGroup] + }); const subnets = props.vpc.selectSubnets(props.subnets); const availabilityZones = new Set(subnets.map(s => s.availabilityZone)); @@ -377,42 +403,48 @@ export class VpcInterfaceEndpoint extends cdk.Construct implements IVpcInterface } /** - * Exports this VPC interface endpoint from the stack. + * Exports this interface VPC endpoint from the stack. */ - public export(): VpcInterfaceEndpointImportProps { + public export(): InterfaceVpcEndpointImportProps { return { vpcEndpointId: new cdk.CfnOutput(this, 'VpcEndpointId', { value: this.vpcEndpointId }).makeImportValue().toString(), - securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId }).makeImportValue().toString() + securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId }).makeImportValue().toString(), + port: new cdk.CfnOutput(this, 'port', { value: this.port }).makeImportValue().toString() }; } } /** - * Construction properties for an ImportedVpcInterfaceEndpoint. + * Construction properties for an ImportedInterfaceVpcEndpoint. */ -export interface VpcInterfaceEndpointImportProps { +export interface InterfaceVpcEndpointImportProps { /** - * The VPC interface endpoint identifier. + * The interface VPC endpoint identifier. */ readonly vpcEndpointId: string; /** - * The identifier of the security group associated with the VPC interface endpoint. + * The identifier of the security group associated with the interface VPC endpoint. */ readonly securityGroupId: string; + + /** + * The port of the service of the interface VPC endpoint. + */ + readonly port: string; } /** * An imported VPC interface endpoint. */ -class ImportedVpcInterfaceEndpoint extends cdk.Construct implements IVpcInterfaceEndpoint { +class ImportedInterfaceVpcEndpoint extends cdk.Construct implements IInterfaceVpcEndpoint { /** - * The VPC interface endpoint identifier. + * The interface VPC endpoint identifier. */ public readonly vpcEndpointId: string; /** - * The identifier of the security group associated with the VPC interface endpoint. + * The identifier of the security group associated with the interface VPC endpoint. */ public readonly securityGroupId: string; @@ -421,7 +453,7 @@ class ImportedVpcInterfaceEndpoint extends cdk.Construct implements IVpcInterfac */ public readonly connections: Connections; - constructor(scope: cdk.Construct, id: string, private readonly props: VpcInterfaceEndpointImportProps) { + constructor(scope: cdk.Construct, id: string, private readonly props: InterfaceVpcEndpointImportProps) { super(scope, id); this.vpcEndpointId = props.vpcEndpointId; @@ -429,14 +461,15 @@ class ImportedVpcInterfaceEndpoint extends cdk.Construct implements IVpcInterfac this.securityGroupId = props.securityGroupId; this.connections = new Connections({ + defaultPortRange: new TcpPortFromAttribute(props.port), securityGroups: [SecurityGroup.import(this, 'SecurityGroup', props)], }); } /** - * Exports this VPC endpoint from the stack. + * Exports this interface VPC endpoint from the stack. */ - public export(): VpcInterfaceEndpointImportProps { + public export(): InterfaceVpcEndpointImportProps { return this.props; } } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 64599c0a1ce33..ea4b228750368 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,6 +1,6 @@ import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk"; import { DEFAULT_SUBNET_NAME, subnetName } from './util'; -import { VpcInterfaceEndpoint, VpcInterfaceEndpointOptions } from './vpc-endpoint'; +import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; import { VpnConnection, VpnConnectionOptions } from './vpn'; export interface IVpcSubnet extends IConstruct { @@ -95,7 +95,7 @@ export interface IVpcNetwork extends IConstruct { /** * Adds a new interface endpoint to this VPC */ - addInterfaceEndpoint(id: string, options: VpcInterfaceEndpointOptions): VpcInterfaceEndpoint + addInterfaceEndpoint(id: string, options: InterfaceVpcEndpointOptions): InterfaceVpcEndpoint /** * Exports this VPC so it can be consumed by another stack. @@ -257,8 +257,8 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Adds a new interface endpoint to this VPC */ - public addInterfaceEndpoint(id: string, options: VpcInterfaceEndpointOptions): VpcInterfaceEndpoint { - return new VpcInterfaceEndpoint(this, id, { + public addInterfaceEndpoint(id: string, options: InterfaceVpcEndpointOptions): InterfaceVpcEndpoint { + return new InterfaceVpcEndpoint(this, id, { vpc: this, ...options }); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 75c979ffe3328..1e0d64efefdca 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -4,7 +4,7 @@ import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, Cfn import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated'; import { NetworkBuilder } from './network-util'; import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId } from './util'; -import { VpcEndpointAwsService, VpcGatewayEndpoint, VpcGatewayEndpointOptions } from './vpc-endpoint'; +import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, GatewayVpcEndpointOptions } from './vpc-endpoint'; import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider'; import { IVpcNetwork, IVpcSubnet, SubnetSelection, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcSubnetImportProps } from './vpc-ref'; import { VpnConnectionOptions, VpnConnectionType } from './vpn'; @@ -149,7 +149,7 @@ export interface VpcNetworkProps { /** * Gateway endpoints to add to this VPC. */ - readonly gatewayEndpoints?: { [id: string]: VpcGatewayEndpointOptions } + readonly gatewayEndpoints?: { [id: string]: GatewayVpcEndpointOptions } } /** @@ -433,8 +433,8 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new gateway endpoint to this VPC */ - public addGatewayEndpoint(id: string, options: VpcGatewayEndpointOptions): VpcGatewayEndpoint { - return new VpcGatewayEndpoint(this, id, { + public addGatewayEndpoint(id: string, options: GatewayVpcEndpointOptions): GatewayVpcEndpoint { + return new GatewayVpcEndpoint(this, id, { vpc: this, ...options }); @@ -443,9 +443,9 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new S3 gateway endpoint to this VPC */ - public addS3Endpoint(id: string, subnets?: SubnetSelection): VpcGatewayEndpoint { - return new VpcGatewayEndpoint(this, id, { - service: VpcEndpointAwsService.S3, + public addS3Endpoint(id: string, subnets?: SubnetSelection): GatewayVpcEndpoint { + return new GatewayVpcEndpoint(this, id, { + service: GatewayVpcEndpointAwsService.S3, vpc: this, subnets }); @@ -454,9 +454,9 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new DynamoDB gateway endpoint to this VPC */ - public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection): VpcGatewayEndpoint { - return new VpcGatewayEndpoint(this, id, { - service: VpcEndpointAwsService.DynamoDb, + public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection): GatewayVpcEndpoint { + return new GatewayVpcEndpoint(this, id, { + service: GatewayVpcEndpointAwsService.DynamoDb, vpc: this, subnets }); diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts index feb2975db08fa..4e9dca508c896 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts @@ -13,14 +13,14 @@ class VpcEndpointStack extends cdk.Stack { const vpc = new ec2.VpcNetwork(this, 'MyVpc', { gatewayEndpoints: { S3: { - service: ec2.VpcEndpointAwsService.S3 + service: ec2.GatewayVpcEndpointAwsService.S3 } } }); // Alternatively gateway endpoints can be added on the VPC const dynamoDbEndpoint = vpc.addGatewayEndpoint('DynamoDbEndpoint', { - service: ec2.VpcEndpointAwsService.DynamoDb + service: ec2.GatewayVpcEndpointAwsService.DynamoDb }); // This allows to customize the endpoint policy @@ -33,12 +33,12 @@ class VpcEndpointStack extends cdk.Stack { // Add an interface endpoint const ecrDockerEndpoint = vpc.addInterfaceEndpoint('EcrDockerEndpoint', { - service: ec2.VpcEndpointAwsService.EcrDocker + service: ec2.InterfaceVpcEndpointAwsService.EcrDocker }); // When working with an interface endpoint, use the connections object to // allow traffic to flow to the endpoint. - ecrDockerEndpoint.connections.allowFromAnyIPv4(new ec2.TcpPort(443)); + ecrDockerEndpoint.connections.allowDefaultPortFromAnyIpv4(); /// !hide } } diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index c537ad81e2747..0c21b2d2add68 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -2,7 +2,8 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { PolicyStatement } from '@aws-cdk/aws-iam'; import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { SubnetType, TcpPort, VpcEndpointAwsService, VpcGatewayEndpoint, VpcInterfaceEndpoint, VpcNetwork } from '../lib'; +// tslint:disable-next-line:max-line-length +import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, SubnetType, VpcNetwork } from '../lib'; export = { 'gateway endpoint': { @@ -14,7 +15,7 @@ export = { new VpcNetwork(stack, 'VpcNetwork', { gatewayEndpoints: { S3: { - service: VpcEndpointAwsService.S3 + service: GatewayVpcEndpointAwsService.S3 } } }); @@ -53,21 +54,6 @@ export = { test.done(); }, - 'throws when creating with a bad endpoint type'(test: Test) { - // GIVEN - const stack = new Stack(); - - // WHEN - const vpc = new VpcNetwork(stack, 'VpcNetwork'); - - // THEN - test.throws(() => vpc.addGatewayEndpoint('Bad', { - service: VpcEndpointAwsService.Ec2 - }), /`Gateway`/); - - test.done(); - }, - 'routing on private and public subnets'(test: Test) { // GIVEN const stack = new Stack(); @@ -76,7 +62,7 @@ export = { new VpcNetwork(stack, 'VpcNetwork', { gatewayEndpoints: { S3: { - service: VpcEndpointAwsService.S3, + service: GatewayVpcEndpointAwsService.S3, subnets: { subnetTypes: [SubnetType.Public, SubnetType.Private] } @@ -132,7 +118,7 @@ export = { const stack = new Stack(); const vpc = new VpcNetwork(stack, 'VpcNetwork'); const endpoint = vpc.addGatewayEndpoint('S3', { - service: VpcEndpointAwsService.S3 + service: GatewayVpcEndpointAwsService.S3 }); // WHEN @@ -169,7 +155,7 @@ export = { const stack = new Stack(); const vpc = new VpcNetwork(stack, 'VpcNetwork'); const endpoint = vpc.addGatewayEndpoint('S3', { - service: VpcEndpointAwsService.S3 + service: GatewayVpcEndpointAwsService.S3 }); // THEN @@ -188,11 +174,11 @@ export = { const stack2 = new Stack(); const vpc = new VpcNetwork(stack1, 'Vpc1'); const endpoint = vpc.addGatewayEndpoint('DynamoDB', { - service: VpcEndpointAwsService.DynamoDb + service: GatewayVpcEndpointAwsService.DynamoDb }); // WHEN - VpcGatewayEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); + GatewayVpcEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); // THEN: No error test.done(); @@ -250,7 +236,7 @@ export = { // WHEN vpc.addInterfaceEndpoint('EcrDocker', { - service: VpcEndpointAwsService.EcrDocker + service: InterfaceVpcEndpointAwsService.EcrDocker }); // THEN @@ -303,21 +289,6 @@ export = { test.done(); }, - 'throws when creating with a bad endpoint type'(test: Test) { - // GIVEN - const stack = new Stack(); - - // WHEN - const vpc = new VpcNetwork(stack, 'VpcNetwork'); - - // THEN - test.throws(() => vpc.addInterfaceEndpoint('Bad', { - service: VpcEndpointAwsService.S3 - }), /`Interface`/); - - test.done(); - }, - 'throws when using more than one subnet per availability zone'(test: Test) { // GIVEN const stack = new Stack(); @@ -333,7 +304,7 @@ export = { // THEN test.throws(() => vpc.addInterfaceEndpoint('Bad', { - service: VpcEndpointAwsService.SecretsManager + service: InterfaceVpcEndpointAwsService.SecretsManager }), /availability zone/); test.done(); @@ -345,12 +316,12 @@ export = { const stack2 = new Stack(); const vpc = new VpcNetwork(stack1, 'Vpc1'); const endpoint = vpc.addInterfaceEndpoint('EC2', { - service: VpcEndpointAwsService.Ec2 + service: InterfaceVpcEndpointAwsService.Ec2 }); // WHEN - const importedEndpoint = VpcInterfaceEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); - importedEndpoint.connections.allowFromAnyIPv4(new TcpPort(443)); + const importedEndpoint = InterfaceVpcEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); + importedEndpoint.connections.allowDefaultPortFromAnyIpv4(); // THEN expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { From b07e2cb3770ceac93fb80f3f2c275a6d14f0398b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 29 Mar 2019 11:22:24 +0100 Subject: [PATCH 08/22] Add policy support for interface endpoints --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 745931286fccb..fb0baf35a0b22 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -17,6 +17,32 @@ export interface IVpcEndpoint extends cdk.IConstruct { readonly vpcEndpointId: string; } +export abstract class VpcEndpoint extends cdk.Construct implements IVpcEndpoint { + public abstract vpcEndpointId: string; + + protected policyDocument?: iam.PolicyDocument; + + /** + * Adds a statement to the policy document of the VPC endpoint. + * + * Not all interface VPC endpoints support policy. For more information + * see https://docs.aws.amazon.com/vpc/latest/userguide/vpce-interface.html + * + * @param statement the IAM statement to add + */ + public addToPolicy(statement: iam.PolicyStatement) { + if (!statement.hasPrincipal) { + throw new Error('Statement must have a `Principal`.'); + } + + if (!this.policyDocument) { + this.policyDocument = new iam.PolicyDocument(); + } + + this.policyDocument.addStatement(statement); + } +} + /** * A gateway VPC endpoint. */ @@ -106,7 +132,7 @@ export interface GatewayVpcEndpointProps extends GatewayVpcEndpointOptions { /** * A gateway VPC endpoint. */ -export class GatewayVpcEndpoint extends cdk.Construct implements IGatewayVpcEndpoint { +export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoint { /** * Imports an existing gateway VPC endpoint. */ @@ -124,8 +150,6 @@ export class GatewayVpcEndpoint extends cdk.Construct implements IGatewayVpcEndp */ public readonly creationTimestamp: string; - private policyDocument?: iam.PolicyDocument; - constructor(scope: cdk.Construct, id: string, props: GatewayVpcEndpointProps) { super(scope, id); @@ -144,23 +168,6 @@ export class GatewayVpcEndpoint extends cdk.Construct implements IGatewayVpcEndp this.creationTimestamp = endpoint.vpcEndpointCreationTimestamp; } - /** - * Adds a statement to the policy document. - * - * @param statement the statement to add - */ - public addToPolicy(statement: iam.PolicyStatement) { - if (!statement.hasPrincipal) { - throw new Error('Statement must have a `Principal`.'); - } - - if (!this.policyDocument) { - this.policyDocument = new iam.PolicyDocument(); - } - - this.policyDocument.addStatement(statement); - } - /** * Exports this gateway VPC endpoint from the stack. */ @@ -326,7 +333,7 @@ export interface IInterfaceVpcEndpoint extends IVpcEndpoint, IConnectable { /** * A interface VPC endpoint. */ -export class InterfaceVpcEndpoint extends cdk.Construct implements IInterfaceVpcEndpoint { +export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEndpoint { /** * Imports an existing interface VPC endpoint. */ @@ -389,6 +396,7 @@ export class InterfaceVpcEndpoint extends cdk.Construct implements IInterfaceVpc const endpoint = new CfnVPCEndpoint(this, 'Resource', { privateDnsEnabled: props.privateDnsEnabled || true, + policyDocument: new cdk.Token(() => this.policyDocument), securityGroupIds: [this.securityGroupId], serviceName: props.service.name, vpcEndpointType: VpcEndpointType.Interface, From 5c3e0d2d8dd1370fe0761ff8aa20f0bc6aaeefd4 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 29 Mar 2019 17:54:42 +0100 Subject: [PATCH 09/22] Fix build --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index fb0baf35a0b22..69998f75fb070 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -18,12 +18,13 @@ export interface IVpcEndpoint extends cdk.IConstruct { } export abstract class VpcEndpoint extends cdk.Construct implements IVpcEndpoint { - public abstract vpcEndpointId: string; + public abstract readonly vpcEndpointId: string; protected policyDocument?: iam.PolicyDocument; /** - * Adds a statement to the policy document of the VPC endpoint. + * Adds a statement to the policy document of the VPC endpoint. The statement + * must have a Principal. * * Not all interface VPC endpoints support policy. For more information * see https://docs.aws.amazon.com/vpc/latest/userguide/vpce-interface.html From 1688c673ca393f69ecdf8619e06e534bd5c2bf7c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 1 Apr 2019 15:24:49 +0200 Subject: [PATCH 10/22] Integrate codebuild vpc support --- packages/@aws-cdk/aws-codebuild/lib/project.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index a891b5400780f..0818ee0b2c8aa 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -872,9 +872,6 @@ export class Project extends ProjectBase { }); this._securityGroups = [securityGroup]; } - const subnetSelection: ec2.SubnetSelection = props.subnetSelection ? props.subnetSelection : { - subnetType: ec2.SubnetType.Private - }; this.addToRoleInlinePolicy(new iam.PolicyStatement() .addAllResources() .addActions( @@ -897,7 +894,7 @@ export class Project extends ProjectBase { .addAction('ec2:CreateNetworkInterfacePermission')); return { vpcId: props.vpc.vpcId, - subnets: props.vpc.subnetIds(subnetSelection).map(s => s), + subnets: props.vpc.subnetIds(props.subnetSelection), securityGroupIds: this._securityGroups.map(s => s.securityGroupId) }; } From af46e05a4f14e6957b3b379475a80f2b221abce7 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 09:53:36 +0200 Subject: [PATCH 11/22] Rename creationTimestamp to vpcEndpointCreationTimestamp --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 69998f75fb070..27dcf976fa274 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -149,7 +149,7 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi /** * The date and time the gateway VPC endpoint was created. */ - public readonly creationTimestamp: string; + public readonly vpcEndpointCreationTimestamp: string; constructor(scope: cdk.Construct, id: string, props: GatewayVpcEndpointProps) { super(scope, id); @@ -166,7 +166,7 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi }); this.vpcEndpointId = endpoint.vpcEndpointId; - this.creationTimestamp = endpoint.vpcEndpointCreationTimestamp; + this.vpcEndpointCreationTimestamp = endpoint.vpcEndpointCreationTimestamp; } /** @@ -350,7 +350,7 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn /** * The date and time the interface VPC endpoint was created. */ - public readonly creationTimestamp: string; + public readonly vpcEndpointCreationTimestamp: string; /** * The DNS entries for the interface VPC endpoint. @@ -406,7 +406,7 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn }); this.vpcEndpointId = endpoint.vpcEndpointId; - this.creationTimestamp = endpoint.vpcEndpointCreationTimestamp; + this.vpcEndpointCreationTimestamp = endpoint.vpcEndpointCreationTimestamp; this.dnsEntries = endpoint.vpcEndpointDnsEntries; this.networkInterfaceIds = endpoint.vpcEndpointNetworkInterfaceIds; } From a3978aa72d70a9ec485d53233d32935107fb1a80 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 10:42:30 +0200 Subject: [PATCH 12/22] Deprecate subnetIds --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 47 ++++++++++--------- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 26 +++++----- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 2 +- .../lib/shared/base-load-balancer.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 2 +- .../aws-rds/lib/rotation-single-user.ts | 2 +- 10 files changed, 46 insertions(+), 43 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 b0003e9abc368..f5b80f0712e24 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -268,7 +268,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`); } - const subnetIds = props.vpc.subnetIds(props.vpcSubnets); + const subnetIds = props.vpc.selectSubnets(props.vpcSubnets).map(s => s.subnetId); const asgProps: CfnAutoScalingGroupProps = { cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, minSize: minCapacity.toString(), diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 0818ee0b2c8aa..0b8d98c7fd223 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -894,7 +894,7 @@ export class Project extends ProjectBase { .addAction('ec2:CreateNetworkInterfacePermission')); return { vpcId: props.vpc.vpcId, - subnets: props.vpc.subnetIds(props.subnetSelection), + subnets: props.vpc.selectSubnets(props.subnetSelection).map(s => s.subnetId), securityGroupIds: this._securityGroups.map(s => s.securityGroupId) }; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index ea4b228750368..dfd7d61859a1b 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -64,16 +64,18 @@ export interface IVpcNetwork extends IConstruct { /** * Return IDs of the subnets appropriate for the given selection strategy * - * Requires that at least once subnet is matched, throws a descriptive + * Requires that at least one subnet is matched, throws a descriptive * error message otherwise. * - * Prefer to use this method over {@link subnets} if you need to pass subnet - * IDs to a CloudFormation Resource. + * @deprecated map over the results of `selectSubnets` */ subnetIds(selection?: SubnetSelection): string[]; /** * Return the subnets appropriate for the placement strategy + * + * Requires that at least one subnet is matched, throws a descriptive + * error message otherwise. */ selectSubnets(selection?: SubnetSelection): IVpcSubnet[]; @@ -219,6 +221,8 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Returns IDs of selected subnets + * + * @deprecated map over the results of `selectSubnets` */ public subnetIds(selection: SubnetSelection = {}): string[] { selection = reifySelectionDefaults(selection); @@ -289,32 +293,31 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { */ public selectSubnets(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); + let subnets: IVpcSubnet[] = []; - // Select by name - if (selection.subnetNames !== undefined) { + if (selection.subnetNames !== undefined) { // Select by name const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; const names = selection.subnetNames; - const nameSubnets = allSubnets.filter(s => names.includes(subnetName(s))); - if (nameSubnets.length === 0) { - throw new Error(`No subnets with names in: ${selection.subnetNames}`); + subnets = allSubnets.filter(s => names.includes(subnetName(s))); + } else if (selection.subnetTypes === undefined) { // No type + subnets = this.privateSubnets; + } else { // Select by type + if (selection.subnetTypes.includes(SubnetType.Public)) { + subnets = [...subnets, ...this.publicSubnets]; + } + if (selection.subnetTypes.includes(SubnetType.Private)) { + subnets = [...subnets, ...this.privateSubnets]; + } + if (selection.subnetTypes.includes(SubnetType.Isolated)) { + subnets = [...subnets, ...this.isolatedSubnets]; } - return nameSubnets; } - // Select by type - if (selection.subnetTypes === undefined) { return this.privateSubnets; } - - let typeSubnets: IVpcSubnet[] = []; - if (selection.subnetTypes.includes(SubnetType.Public)) { - typeSubnets = [...typeSubnets, ...this.publicSubnets]; - } - if (selection.subnetTypes.includes(SubnetType.Private)) { - typeSubnets = [...typeSubnets, ...this.privateSubnets]; - } - if (selection.subnetTypes.includes(SubnetType.Isolated)) { - typeSubnets = [...typeSubnets, ...this.isolatedSubnets]; + if (subnets.length === 0) { + throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); } - return typeSubnets; + + return subnets; } } diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index fd5f7929f4bd0..97148789f7fa5 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -602,10 +602,10 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.subnetIds(); + const nets = vpc.selectSubnets(); // THEN - test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); + test.deepEqual(nets, vpc.privateSubnets); test.done(); }, @@ -615,10 +615,10 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Public] }); + const nets = vpc.selectSubnets({ subnetTypes: [SubnetType.Public] }); // THEN - test.deepEqual(nets, vpc.publicSubnets.map(s => s.subnetId)); + test.deepEqual(nets, vpc.publicSubnets); test.done(); }, @@ -634,10 +634,10 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); + const nets = vpc.selectSubnets({ subnetTypes: [SubnetType.Isolated] }); // THEN - test.deepEqual(nets, vpc.isolatedSubnets.map(s => s.subnetId)); + test.deepEqual(nets, vpc.isolatedSubnets); test.done(); }, @@ -653,10 +653,10 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetNames: ['DontTalkToMe'] }); + const nets = vpc.selectSubnets({ subnetNames: ['DontTalkToMe'] }); // THEN - test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); + test.deepEqual(nets, vpc.privateSubnets); test.done(); }, @@ -670,7 +670,7 @@ export = { }); test.throws(() => { - vpc.subnetIds(); + vpc.selectSubnets(); }, /There are no 'Private' subnets in this VPC/); test.done(); @@ -734,11 +734,11 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); + const nets = importedVpc.selectSubnets({ subnetTypes: [SubnetType.Isolated] }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets.map(s => s.subnetId)); + test.deepEqual(nets, importedVpc.isolatedSubnets); test.done(); }, @@ -757,11 +757,11 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetNames: [isolatedName] }); + const nets = importedVpc.selectSubnets({ subnetNames: [isolatedName] }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets.map(s => s.subnetId)); + test.deepEqual(nets, importedVpc.isolatedSubnets); } test.done(); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 7ce18acf892c8..1f2f76a4cbfdb 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -189,7 +189,7 @@ export abstract class BaseService extends cdk.Construct this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - subnets: vpc.subnetIds(vpcSubnets), + subnets: vpc.selectSubnets(vpcSubnets).map(s => s.subnetId), securityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]).toList(), } }; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 10ef87c88e1a8..a7ab7863b89d3 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -163,7 +163,7 @@ export class Cluster extends ClusterBase { // Get subnetIds for all selected subnets const placements = props.vpcSubnets || { subnetTypes: [ec2.SubnetType.Public, ec2.SubnetType.Private] }; - const subnetIds = this.vpc.subnetIds(placements); + const subnetIds = this.vpc.selectSubnets(placements).map(s => s.subnetId); const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 789cfb45a286d..cfc9f4a5c010c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -101,7 +101,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const vpcSubnets = ifUndefined(baseProps.vpcSubnets, { subnetTypes: internetFacing ? [ec2.SubnetType.Public] : [ec2.SubnetType.Private] }); - const subnets = baseProps.vpc.subnetIds(vpcSubnets); + const subnets = baseProps.vpc.selectSubnets(vpcSubnets).map(s => s.subnetId); this.vpc = baseProps.vpc; diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 87ad0335cf238..d77e304919e0f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -544,7 +544,7 @@ export class Function extends FunctionBase { // won't work because the ENIs don't get a Public IP. // Why are we not simply forcing vpcSubnets? Because you might still be choosing // Isolated networks or selecting among 2 sets of Private subnets by name. - const subnetIds = props.vpc.subnetIds(props.vpcSubnets); + const subnetIds = props.vpc.selectSubnets(props.vpcSubnets).map(s => s.subnetId); const publicSubnetIds = new Set(props.vpc.publicSubnets.map(s => s.subnetId)); for (const subnetId of subnetIds) { if (publicSubnetIds.has(subnetId)) { diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index c31e11e3cee64..e7dde46018c7b 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -235,7 +235,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu this.vpc = props.instanceProps.vpc; this.vpcSubnets = props.instanceProps.vpcSubnets; - const subnetIds = props.instanceProps.vpc.subnetIds(props.instanceProps.vpcSubnets); + const subnetIds = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).map(s => s.subnetId); // Cannot test whether the subnets are in different AZs, but at least we can test the amount. if (subnetIds.length < 2) { diff --git a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts index a892fcf021d7e..9cf33b8e8d91f 100644 --- a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts @@ -139,7 +139,7 @@ export class RotationSingleUser extends cdk.Construct { vpc: props.vpc }); - const vpcSubnetIds = props.vpc.subnetIds(props.vpcSubnets); + const vpcSubnetIds = props.vpc.selectSubnets(props.vpcSubnets).map(s => s.subnetId); props.target.connections.allowDefaultPortFrom(securityGroup); From 6235f316af755778bacab544f8654af822421c9e Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 12:38:21 +0200 Subject: [PATCH 13/22] Revert "Deprecate subnetIds" This reverts commit a3978aa72d70a9ec485d53233d32935107fb1a80. --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 47 +++++++++---------- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 26 +++++----- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 2 +- .../lib/shared/base-load-balancer.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 2 +- .../aws-rds/lib/rotation-single-user.ts | 2 +- 10 files changed, 43 insertions(+), 46 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 f5b80f0712e24..b0003e9abc368 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -268,7 +268,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`); } - const subnetIds = props.vpc.selectSubnets(props.vpcSubnets).map(s => s.subnetId); + const subnetIds = props.vpc.subnetIds(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, minSize: minCapacity.toString(), diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 0b8d98c7fd223..0818ee0b2c8aa 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -894,7 +894,7 @@ export class Project extends ProjectBase { .addAction('ec2:CreateNetworkInterfacePermission')); return { vpcId: props.vpc.vpcId, - subnets: props.vpc.selectSubnets(props.subnetSelection).map(s => s.subnetId), + subnets: props.vpc.subnetIds(props.subnetSelection), securityGroupIds: this._securityGroups.map(s => s.securityGroupId) }; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index dfd7d61859a1b..ea4b228750368 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -64,18 +64,16 @@ export interface IVpcNetwork extends IConstruct { /** * Return IDs of the subnets appropriate for the given selection strategy * - * Requires that at least one subnet is matched, throws a descriptive + * Requires that at least once subnet is matched, throws a descriptive * error message otherwise. * - * @deprecated map over the results of `selectSubnets` + * Prefer to use this method over {@link subnets} if you need to pass subnet + * IDs to a CloudFormation Resource. */ subnetIds(selection?: SubnetSelection): string[]; /** * Return the subnets appropriate for the placement strategy - * - * Requires that at least one subnet is matched, throws a descriptive - * error message otherwise. */ selectSubnets(selection?: SubnetSelection): IVpcSubnet[]; @@ -221,8 +219,6 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Returns IDs of selected subnets - * - * @deprecated map over the results of `selectSubnets` */ public subnetIds(selection: SubnetSelection = {}): string[] { selection = reifySelectionDefaults(selection); @@ -293,31 +289,32 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { */ public selectSubnets(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); - let subnets: IVpcSubnet[] = []; - if (selection.subnetNames !== undefined) { // Select by name + // Select by name + if (selection.subnetNames !== undefined) { const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; const names = selection.subnetNames; - subnets = allSubnets.filter(s => names.includes(subnetName(s))); - } else if (selection.subnetTypes === undefined) { // No type - subnets = this.privateSubnets; - } else { // Select by type - if (selection.subnetTypes.includes(SubnetType.Public)) { - subnets = [...subnets, ...this.publicSubnets]; - } - if (selection.subnetTypes.includes(SubnetType.Private)) { - subnets = [...subnets, ...this.privateSubnets]; - } - if (selection.subnetTypes.includes(SubnetType.Isolated)) { - subnets = [...subnets, ...this.isolatedSubnets]; + const nameSubnets = allSubnets.filter(s => names.includes(subnetName(s))); + if (nameSubnets.length === 0) { + throw new Error(`No subnets with names in: ${selection.subnetNames}`); } + return nameSubnets; } - if (subnets.length === 0) { - throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); - } + // Select by type + if (selection.subnetTypes === undefined) { return this.privateSubnets; } - return subnets; + let typeSubnets: IVpcSubnet[] = []; + if (selection.subnetTypes.includes(SubnetType.Public)) { + typeSubnets = [...typeSubnets, ...this.publicSubnets]; + } + if (selection.subnetTypes.includes(SubnetType.Private)) { + typeSubnets = [...typeSubnets, ...this.privateSubnets]; + } + if (selection.subnetTypes.includes(SubnetType.Isolated)) { + typeSubnets = [...typeSubnets, ...this.isolatedSubnets]; + } + return typeSubnets; } } diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 97148789f7fa5..fd5f7929f4bd0 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -602,10 +602,10 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.selectSubnets(); + const nets = vpc.subnetIds(); // THEN - test.deepEqual(nets, vpc.privateSubnets); + test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); test.done(); }, @@ -615,10 +615,10 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.selectSubnets({ subnetTypes: [SubnetType.Public] }); + const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Public] }); // THEN - test.deepEqual(nets, vpc.publicSubnets); + test.deepEqual(nets, vpc.publicSubnets.map(s => s.subnetId)); test.done(); }, @@ -634,10 +634,10 @@ export = { }); // WHEN - const nets = vpc.selectSubnets({ subnetTypes: [SubnetType.Isolated] }); + const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); // THEN - test.deepEqual(nets, vpc.isolatedSubnets); + test.deepEqual(nets, vpc.isolatedSubnets.map(s => s.subnetId)); test.done(); }, @@ -653,10 +653,10 @@ export = { }); // WHEN - const nets = vpc.selectSubnets({ subnetNames: ['DontTalkToMe'] }); + const nets = vpc.subnetIds({ subnetNames: ['DontTalkToMe'] }); // THEN - test.deepEqual(nets, vpc.privateSubnets); + test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); test.done(); }, @@ -670,7 +670,7 @@ export = { }); test.throws(() => { - vpc.selectSubnets(); + vpc.subnetIds(); }, /There are no 'Private' subnets in this VPC/); test.done(); @@ -734,11 +734,11 @@ export = { }); // WHEN - const nets = importedVpc.selectSubnets({ subnetTypes: [SubnetType.Isolated] }); + const nets = importedVpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets); + test.deepEqual(nets, importedVpc.isolatedSubnets.map(s => s.subnetId)); test.done(); }, @@ -757,11 +757,11 @@ export = { }); // WHEN - const nets = importedVpc.selectSubnets({ subnetNames: [isolatedName] }); + const nets = importedVpc.subnetIds({ subnetNames: [isolatedName] }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets); + test.deepEqual(nets, importedVpc.isolatedSubnets.map(s => s.subnetId)); } test.done(); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 1f2f76a4cbfdb..7ce18acf892c8 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -189,7 +189,7 @@ export abstract class BaseService extends cdk.Construct this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - subnets: vpc.selectSubnets(vpcSubnets).map(s => s.subnetId), + subnets: vpc.subnetIds(vpcSubnets), securityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]).toList(), } }; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index a7ab7863b89d3..10ef87c88e1a8 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -163,7 +163,7 @@ export class Cluster extends ClusterBase { // Get subnetIds for all selected subnets const placements = props.vpcSubnets || { subnetTypes: [ec2.SubnetType.Public, ec2.SubnetType.Private] }; - const subnetIds = this.vpc.selectSubnets(placements).map(s => s.subnetId); + const subnetIds = this.vpc.subnetIds(placements); const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index cfc9f4a5c010c..789cfb45a286d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -101,7 +101,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const vpcSubnets = ifUndefined(baseProps.vpcSubnets, { subnetTypes: internetFacing ? [ec2.SubnetType.Public] : [ec2.SubnetType.Private] }); - const subnets = baseProps.vpc.selectSubnets(vpcSubnets).map(s => s.subnetId); + const subnets = baseProps.vpc.subnetIds(vpcSubnets); this.vpc = baseProps.vpc; diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index d77e304919e0f..87ad0335cf238 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -544,7 +544,7 @@ export class Function extends FunctionBase { // won't work because the ENIs don't get a Public IP. // Why are we not simply forcing vpcSubnets? Because you might still be choosing // Isolated networks or selecting among 2 sets of Private subnets by name. - const subnetIds = props.vpc.selectSubnets(props.vpcSubnets).map(s => s.subnetId); + const subnetIds = props.vpc.subnetIds(props.vpcSubnets); const publicSubnetIds = new Set(props.vpc.publicSubnets.map(s => s.subnetId)); for (const subnetId of subnetIds) { if (publicSubnetIds.has(subnetId)) { diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index e7dde46018c7b..c31e11e3cee64 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -235,7 +235,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu this.vpc = props.instanceProps.vpc; this.vpcSubnets = props.instanceProps.vpcSubnets; - const subnetIds = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).map(s => s.subnetId); + const subnetIds = props.instanceProps.vpc.subnetIds(props.instanceProps.vpcSubnets); // Cannot test whether the subnets are in different AZs, but at least we can test the amount. if (subnetIds.length < 2) { diff --git a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts index 9cf33b8e8d91f..a892fcf021d7e 100644 --- a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts @@ -139,7 +139,7 @@ export class RotationSingleUser extends cdk.Construct { vpc: props.vpc }); - const vpcSubnetIds = props.vpc.selectSubnets(props.vpcSubnets).map(s => s.subnetId); + const vpcSubnetIds = props.vpc.subnetIds(props.vpcSubnets); props.target.connections.allowDefaultPortFrom(securityGroup); From 2758a05573386fca24164fe8f067d36766d1cc10 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 12:39:11 +0200 Subject: [PATCH 14/22] Revert "Integrate codebuild vpc support" This reverts commit 1688c673ca393f69ecdf8619e06e534bd5c2bf7c. --- packages/@aws-cdk/aws-codebuild/lib/project.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 0818ee0b2c8aa..a891b5400780f 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -872,6 +872,9 @@ export class Project extends ProjectBase { }); this._securityGroups = [securityGroup]; } + const subnetSelection: ec2.SubnetSelection = props.subnetSelection ? props.subnetSelection : { + subnetType: ec2.SubnetType.Private + }; this.addToRoleInlinePolicy(new iam.PolicyStatement() .addAllResources() .addActions( @@ -894,7 +897,7 @@ export class Project extends ProjectBase { .addAction('ec2:CreateNetworkInterfacePermission')); return { vpcId: props.vpc.vpcId, - subnets: props.vpc.subnetIds(props.subnetSelection), + subnets: props.vpc.subnetIds(subnetSelection).map(s => s), securityGroupIds: this._securityGroups.map(s => s.securityGroupId) }; } From e2827d3e4d7de9574b8840d69faa186ebaaf473c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 13:11:27 +0200 Subject: [PATCH 15/22] Revert "Extend SubnetSelection" This reverts commit c799be0ea183548bcea1fe00c00747ce427b9ee1. --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- .../test/test.auto-scaling-group.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 17 +++-- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 65 ++++++++----------- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 20 ++++-- .../aws-ec2/test/test.vpc-endpoint.ts | 7 +- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 27 ++++---- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- .../aws-ecs/test/ec2/test.ec2-service.ts | 4 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 16 +++-- .../test/example.ssh-into-nodes.lit.ts | 2 +- .../lib/shared/base-load-balancer.ts | 2 +- .../aws-lambda/test/test.vpc-lambda.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 4 +- .../@aws-cdk/aws-rds/test/integ.cluster.ts | 2 +- 15 files changed, 94 insertions(+), 80 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 b0003e9abc368..c8429013018a7 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -292,7 +292,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup }; if (!props.vpc.isPublicSubnets(subnetIds) && props.associatePublicIpAddress) { - throw new Error("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetTypes: [SubnetType.Public] })"); + throw new Error("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.Public })"); } this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps); 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 de3a07de76cd5..f833fe4eec5b7 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 @@ -419,7 +419,7 @@ export = { maxCapacity: 0, desiredCapacity: 0, - vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] }, + vpcSubnets: { subnetType: ec2.SubnetType.Public }, associatePublicIpAddress: true, }); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 27dcf976fa274..b73d778146773 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -5,7 +5,7 @@ import { CfnVPCEndpoint } from './ec2.generated'; import { SecurityGroup } from './security-group'; import { TcpPort, TcpPortFromAttribute } from './security-group-rule'; import { VpcSubnet } from './vpc'; -import { IVpcNetwork, SubnetSelection } from './vpc-ref'; +import { IVpcNetwork, IVpcSubnet, SubnetSelection } from './vpc-ref'; /** * A VPC endpoint. @@ -117,7 +117,7 @@ export interface GatewayVpcEndpointOptions { * * @default private subnets */ - readonly subnets?: SubnetSelection + readonly subnets?: SubnetSelection[] } /** @@ -154,7 +154,15 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi constructor(scope: cdk.Construct, id: string, props: GatewayVpcEndpointProps) { super(scope, id); - const subnets = props.vpc.selectSubnets(props.subnets); + let subnets: IVpcSubnet[] = []; + if (props.subnets) { + for (const selection of props.subnets) { + subnets.push(...props.vpc.subnets(selection)); + } + } else { + subnets = props.vpc.subnets(); + } + const routeTableIds = (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId); const endpoint = new CfnVPCEndpoint(this, 'Resource', { @@ -388,7 +396,8 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn securityGroups: [securityGroup] }); - const subnets = props.vpc.selectSubnets(props.subnets); + const subnets = props.vpc.subnets(props.subnets); + const availabilityZones = new Set(subnets.map(s => s.availabilityZone)); if (availabilityZones.size !== subnets.length) { diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index ea4b228750368..8ce78dc45b2fe 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -75,7 +75,7 @@ export interface IVpcNetwork extends IConstruct { /** * Return the subnets appropriate for the placement strategy */ - selectSubnets(selection?: SubnetSelection): IVpcSubnet[]; + subnets(selection?: SubnetSelection): IVpcSubnet[]; /** * Return a dependable object representing internet connectivity for the given subnets @@ -152,16 +152,16 @@ export enum SubnetType { */ export interface SubnetSelection { /** - * Place the instances in the subnets of the given types + * Place the instances in the subnets of the given type * * At most one of `subnetType` and `subnetName` can be supplied. * * @default SubnetType.Private */ - readonly subnetTypes?: SubnetType[]; + readonly subnetType?: SubnetType; /** - * Place the instances in the subnets with the given names + * Place the instances in the subnets with the given name * * (This is the name supplied in subnetConfiguration). * @@ -169,7 +169,7 @@ export interface SubnetSelection { * * @default name */ - readonly subnetNames?: string[]; + readonly subnetName?: string; } /** @@ -223,7 +223,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { public subnetIds(selection: SubnetSelection = {}): string[] { selection = reifySelectionDefaults(selection); - const nets = this.selectSubnets(selection); + const nets = this.subnets(selection); if (nets.length === 0) { throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); } @@ -238,7 +238,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { selection = reifySelectionDefaults(selection); const ret = new CompositeDependable(); - for (const subnet of this.selectSubnets(selection)) { + for (const subnet of this.subnets(selection)) { ret.add(subnet.internetConnectivityEstablished); } return ret; @@ -287,34 +287,27 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Return the subnets appropriate for the placement strategy */ - public selectSubnets(selection: SubnetSelection = {}): IVpcSubnet[] { + public subnets(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); // Select by name - if (selection.subnetNames !== undefined) { - const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; - const names = selection.subnetNames; - const nameSubnets = allSubnets.filter(s => names.includes(subnetName(s))); - if (nameSubnets.length === 0) { - throw new Error(`No subnets with names in: ${selection.subnetNames}`); + if (selection.subnetName !== undefined) { + const allSubnets = this.privateSubnets.concat(this.publicSubnets).concat(this.isolatedSubnets); + const selectedSubnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); + if (selectedSubnets.length === 0) { + throw new Error(`No subnets with name: ${selection.subnetName}`); } - return nameSubnets; + return selectedSubnets; } // Select by type - if (selection.subnetTypes === undefined) { return this.privateSubnets; } + if (selection.subnetType === undefined) { return this.privateSubnets; } - let typeSubnets: IVpcSubnet[] = []; - if (selection.subnetTypes.includes(SubnetType.Public)) { - typeSubnets = [...typeSubnets, ...this.publicSubnets]; - } - if (selection.subnetTypes.includes(SubnetType.Private)) { - typeSubnets = [...typeSubnets, ...this.privateSubnets]; - } - if (selection.subnetTypes.includes(SubnetType.Isolated)) { - typeSubnets = [...typeSubnets, ...this.isolatedSubnets]; - } - return typeSubnets; + return { + [SubnetType.Isolated]: this.isolatedSubnets, + [SubnetType.Private]: this.privateSubnets, + [SubnetType.Public]: this.publicSubnets, + }[selection.subnetType]; } } @@ -399,12 +392,12 @@ export interface VpcSubnetImportProps { * Returns "private subnets" by default. */ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { - if (placement.subnetTypes !== undefined && placement.subnetNames !== undefined) { + if (placement.subnetType !== undefined && placement.subnetName !== undefined) { throw new Error('Only one of subnetType and subnetName can be supplied'); } - if (placement.subnetTypes === undefined && placement.subnetNames === undefined) { - return { subnetTypes: [SubnetType.Private] }; + if (placement.subnetType === undefined && placement.subnetName === undefined) { + return { subnetType: SubnetType.Private }; } return placement; @@ -414,19 +407,15 @@ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { * Describe the given placement strategy */ function describeSelection(placement: SubnetSelection): string { - if (placement.subnetTypes !== undefined) { - return `'${joinCommaOr(placement.subnetTypes.map(type => DEFAULT_SUBNET_NAME[type]))}' subnets`; + if (placement.subnetType !== undefined) { + return `'${DEFAULT_SUBNET_NAME[placement.subnetType]}' subnets`; } - if (placement.subnetNames !== undefined) { - return `subnets named '${joinCommaOr(placement.subnetNames)}'`; + if (placement.subnetName !== undefined) { + return `subnets named '${placement.subnetName}'`; } return JSON.stringify(placement); } -function joinCommaOr(array: string[]) { - return [array.slice(0, -1).join(', '), array.slice(-1)[0]].join(array.length < 2 ? '' : ' or '); -} - class CompositeDependable implements IDependable { private readonly dependables = new Array(); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index fc4112db7d572..cad636b271fff 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -144,7 +144,7 @@ export interface VpcNetworkProps { * * @default on the route tables associated with private subnets */ - readonly vpnRoutePropagation?: SubnetSelection + readonly vpnRoutePropagation?: SubnetType[] /** * Gateway endpoints to add to this VPC. @@ -414,7 +414,17 @@ export class VpcNetwork extends VpcNetworkBase { this.vpnGatewayId = vpnGateway.vpnGatewayName; // Propagate routes on route tables associated with the right subnets - const subnets = this.selectSubnets(props.vpnRoutePropagation); + const vpnRoutePropagation = props.vpnRoutePropagation || [SubnetType.Private]; + let subnets: IVpcSubnet[] = []; + if (vpnRoutePropagation.includes(SubnetType.Public)) { + subnets = [...subnets, ...this.publicSubnets]; + } + if (vpnRoutePropagation.includes(SubnetType.Private)) { + subnets = [...subnets, ...this.privateSubnets]; + } + if (vpnRoutePropagation.includes(SubnetType.Isolated)) { + subnets = [...subnets, ...this.isolatedSubnets]; + } const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { routeTableIds: (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId), vpnGatewayId: this.vpnGatewayId @@ -455,7 +465,7 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new S3 gateway endpoint to this VPC */ - public addS3Endpoint(id: string, subnets?: SubnetSelection): GatewayVpcEndpoint { + public addS3Endpoint(id: string, subnets?: SubnetSelection[]): GatewayVpcEndpoint { return new GatewayVpcEndpoint(this, id, { service: GatewayVpcEndpointAwsService.S3, vpc: this, @@ -466,7 +476,7 @@ export class VpcNetwork extends VpcNetworkBase { /** * Adds a new DynamoDB gateway endpoint to this VPC */ - public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection): GatewayVpcEndpoint { + public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection[]): GatewayVpcEndpoint { return new GatewayVpcEndpoint(this, id, { service: GatewayVpcEndpointAwsService.DynamoDb, vpc: this, @@ -504,7 +514,7 @@ export class VpcNetwork extends VpcNetworkBase { let natSubnets: VpcPublicSubnet[]; if (placement) { - const subnets = this.selectSubnets(placement); + const subnets = this.subnets(placement); for (const sub of subnets) { if (this.publicSubnets.indexOf(sub) === -1) { throw new Error(`natGatewayPlacement ${placement} contains non public subnet ${sub}`); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index 0c21b2d2add68..cfd79a5c13036 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -63,9 +63,10 @@ export = { gatewayEndpoints: { S3: { service: GatewayVpcEndpointAwsService.S3, - subnets: { - subnetTypes: [SubnetType.Public, SubnetType.Private] - } + subnets: [ + { subnetType: SubnetType.Public }, + { subnetType: SubnetType.Private } + ] } } }); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index fd5f7929f4bd0..5f2bc2fb68578 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -222,7 +222,7 @@ export = { } test.done(); }, - "with custom subnets and natGateways = 2 there should be only two NATGW"(test: Test) { + "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', @@ -358,7 +358,7 @@ export = { }, ], natGatewaySubnets: { - subnetNames: ['egress'] + subnetName: 'egress' }, }); expect(stack).to(countResources("AWS::EC2::NatGateway", 3)); @@ -386,7 +386,7 @@ export = { }, ], natGatewaySubnets: { - subnetNames: ['notthere'], + subnetName: 'notthere', }, })); test.done(); @@ -439,9 +439,7 @@ export = { { subnetType: SubnetType.Isolated, name: 'Isolated' }, ], vpnGateway: true, - vpnRoutePropagation: { - subnetTypes: [SubnetType.Isolated] - } + vpnRoutePropagation: [SubnetType.Isolated] }); expect(stack).to(haveResource('AWS::EC2::VPNGatewayRoutePropagation', { @@ -471,9 +469,10 @@ export = { { subnetType: SubnetType.Isolated, name: 'Isolated' }, ], vpnGateway: true, - vpnRoutePropagation: { - subnetTypes: [SubnetType.Private, SubnetType.Isolated] - } + vpnRoutePropagation: [ + SubnetType.Private, + SubnetType.Isolated + ] }); expect(stack).to(haveResource('AWS::EC2::VPNGatewayRoutePropagation', { @@ -615,7 +614,7 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Public] }); + const nets = vpc.subnetIds({ subnetType: SubnetType.Public }); // THEN test.deepEqual(nets, vpc.publicSubnets.map(s => s.subnetId)); @@ -634,7 +633,7 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); + const nets = vpc.subnetIds({ subnetType: SubnetType.Isolated }); // THEN test.deepEqual(nets, vpc.isolatedSubnets.map(s => s.subnetId)); @@ -653,7 +652,7 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetNames: ['DontTalkToMe'] }); + const nets = vpc.subnetIds({ subnetName: 'DontTalkToMe' }); // THEN test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); @@ -734,7 +733,7 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetTypes: [SubnetType.Isolated] }); + const nets = importedVpc.subnetIds({ subnetType: SubnetType.Isolated }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); @@ -757,7 +756,7 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetNames: [isolatedName] }); + const nets = importedVpc.subnetIds({ subnetName: isolatedName }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 7ce18acf892c8..062312d6cfb89 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -179,7 +179,7 @@ export abstract class BaseService extends cdk.Construct // tslint:disable-next-line:max-line-length protected configureAwsVpcNetworking(vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { if (vpcSubnets === undefined) { - vpcSubnets = { subnetTypes: assignPublicIp ? [ec2.SubnetType.Public] : [ec2.SubnetType.Private] }; + vpcSubnets = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index a6fc9e1009870..45eb699e9785e 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -168,7 +168,7 @@ export = { cluster, taskDefinition, vpcSubnets: { - subnetTypes: [ec2.SubnetType.Public] + subnetType: ec2.SubnetType.Public } }); }); @@ -249,7 +249,7 @@ export = { cluster, taskDefinition, vpcSubnets: { - subnetTypes: [ec2.SubnetType.Public] + subnetType: ec2.SubnetType.Public } }); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 10ef87c88e1a8..174d3eff33509 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -27,13 +27,13 @@ export interface ClusterProps { * * ```ts * vpcSubnets: [ - * { subnetTypes: [ec2.SubnetType.Private] } + * { subnetType: ec2.SubnetType.Private } * ] * ``` * * @default All public and private subnets */ - readonly vpcSubnets?: ec2.SubnetSelection; + readonly vpcSubnets?: ec2.SubnetSelection[]; /** * Role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf. @@ -162,8 +162,8 @@ export class Cluster extends ClusterBase { }); // Get subnetIds for all selected subnets - const placements = props.vpcSubnets || { subnetTypes: [ec2.SubnetType.Public, ec2.SubnetType.Private] }; - const subnetIds = this.vpc.subnetIds(placements); + const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.Public }, { subnetType: ec2.SubnetType.Private }]; + const subnetIds = flatMap(placements, p => this.vpc.subnetIds(p)); const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, @@ -332,3 +332,11 @@ class ImportedCluster extends ClusterBase { } } } + +function flatMap(xs: T[], f: (x: T) => U[]): U[] { + const ret = new Array(); + for (const x of xs) { + ret.push(...f(x)); + } + return ret; +} diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index dddf1547be908..5d188d352cadf 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -15,7 +15,7 @@ class EksClusterStack extends cdk.Stack { /// !show const asg = cluster.addCapacity('Nodes', { instanceType: new ec2.InstanceType('t2.medium'), - vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] }, + vpcSubnets: { subnetType: ec2.SubnetType.Public }, keyName: 'my-key-name', }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 789cfb45a286d..2d2791a1a6f9f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -99,7 +99,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const internetFacing = ifUndefined(baseProps.internetFacing, false); const vpcSubnets = ifUndefined(baseProps.vpcSubnets, - { subnetTypes: internetFacing ? [ec2.SubnetType.Public] : [ec2.SubnetType.Private] }); + { subnetType: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private }); const subnets = baseProps.vpc.subnetIds(vpcSubnets); diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts index 63a4790a00d7d..b069c11a8d2cc 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts @@ -137,7 +137,7 @@ export = { handler: 'index.handler', runtime: lambda.Runtime.NodeJS610, vpc, - vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] } + vpcSubnets: { subnetType: ec2.SubnetType.Public } }); }); diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index c31e11e3cee64..24d547b0262c1 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -312,9 +312,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu props.clusterIdentifier != null ? `${props.clusterIdentifier}instance${instanceIndex}` : undefined; - const publiclyAccessible = props.instanceProps.vpcSubnets && - props.instanceProps.vpcSubnets.subnetTypes && - props.instanceProps.vpcSubnets.subnetTypes.includes(ec2.SubnetType.Public); + const publiclyAccessible = props.instanceProps.vpcSubnets && props.instanceProps.vpcSubnets.subnetType === ec2.SubnetType.Public; const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index acd01aad770e9..281f22ed4e89b 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -24,7 +24,7 @@ const cluster = new DatabaseCluster(stack, 'Database', { }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), - vpcSubnets: { subnetTypes: [ec2.SubnetType.Public] }, + vpcSubnets: { subnetType: ec2.SubnetType.Public }, vpc }, parameterGroup: params, From 8101b6b58547b8bb6177bc19c011c23a12286230 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 3 Apr 2019 17:45:25 +0200 Subject: [PATCH 16/22] Use subnetSelection[] and selectSubnetIds --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 5 +- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 23 ++--- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 95 ++++++++++++------- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 19 +--- .../aws-ec2/test/test.vpc-endpoint.ts | 8 +- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 28 ++++-- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 10 +- .../lib/shared/base-load-balancer.ts | 2 +- packages/@aws-cdk/aws-lambda/lib/function.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 2 +- .../aws-rds/lib/rotation-single-user.ts | 2 +- 13 files changed, 103 insertions(+), 97 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 c8429013018a7..e355678772760 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -268,7 +268,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`); } - const subnetIds = props.vpc.subnetIds(props.vpcSubnets); + const subnetIds = props.vpc.selectSubnetIds(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, minSize: minCapacity.toString(), diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index a891b5400780f..44a6cd7a17e30 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -872,9 +872,6 @@ export class Project extends ProjectBase { }); this._securityGroups = [securityGroup]; } - const subnetSelection: ec2.SubnetSelection = props.subnetSelection ? props.subnetSelection : { - subnetType: ec2.SubnetType.Private - }; this.addToRoleInlinePolicy(new iam.PolicyStatement() .addAllResources() .addActions( @@ -897,7 +894,7 @@ export class Project extends ProjectBase { .addAction('ec2:CreateNetworkInterfacePermission')); return { vpcId: props.vpc.vpcId, - subnets: props.vpc.subnetIds(subnetSelection).map(s => s), + subnets: props.vpc.selectSubnetIds(props.subnetSelection), securityGroupIds: this._securityGroups.map(s => s.securityGroupId) }; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index b73d778146773..6aa4220102c83 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -4,8 +4,7 @@ import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; import { SecurityGroup } from './security-group'; import { TcpPort, TcpPortFromAttribute } from './security-group-rule'; -import { VpcSubnet } from './vpc'; -import { IVpcNetwork, IVpcSubnet, SubnetSelection } from './vpc-ref'; +import { IVpcNetwork, SubnetSelection, SubnetType } from './vpc-ref'; /** * A VPC endpoint. @@ -154,16 +153,8 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi constructor(scope: cdk.Construct, id: string, props: GatewayVpcEndpointProps) { super(scope, id); - let subnets: IVpcSubnet[] = []; - if (props.subnets) { - for (const selection of props.subnets) { - subnets.push(...props.vpc.subnets(selection)); - } - } else { - subnets = props.vpc.subnets(); - } - - const routeTableIds = (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId); + const subnets = props.subnets || [{ subnetType: SubnetType.Private }]; + const routeTableIds = [...new Set(Array().concat(...subnets.map(s => props.vpc.selectRouteTableIds(s))))]; const endpoint = new CfnVPCEndpoint(this, 'Resource', { policyDocument: new cdk.Token(() => this.policyDocument), @@ -396,11 +387,11 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn securityGroups: [securityGroup] }); - const subnets = props.vpc.subnets(props.subnets); + const subnetIds = props.vpc.selectSubnetIds(props.subnets); - const availabilityZones = new Set(subnets.map(s => s.availabilityZone)); + const availabilityZones = props.vpc.selectSubnetAZs(props.subnets); - if (availabilityZones.size !== subnets.length) { + if (availabilityZones.length !== subnetIds.length) { throw new Error('Only one subnet per availability zone is allowed.'); } @@ -410,7 +401,7 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn securityGroupIds: [this.securityGroupId], serviceName: props.service.name, vpcEndpointType: VpcEndpointType.Interface, - subnetIds: subnets.map(s => s.subnetId), + subnetIds, vpcId: props.vpc.vpcId }); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 8ce78dc45b2fe..9b2df00fe65ab 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,5 +1,6 @@ -import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk"; +import { Construct, IConstruct, IDependable } from '@aws-cdk/cdk'; import { DEFAULT_SUBNET_NAME, subnetName } from './util'; +import { VpcSubnet } from './vpc'; import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; import { VpnConnection, VpnConnectionOptions } from './vpn'; @@ -70,12 +71,23 @@ export interface IVpcNetwork extends IConstruct { * Prefer to use this method over {@link subnets} if you need to pass subnet * IDs to a CloudFormation Resource. */ - subnetIds(selection?: SubnetSelection): string[]; + selectSubnetIds(selection?: SubnetSelection): string[]; /** - * Return the subnets appropriate for the placement strategy + * Return IDs of the route tables appropriate for the given selection strategy + * + * Requires that at least once subnet is matched, throws a descriptive + * error message otherwise. */ - subnets(selection?: SubnetSelection): IVpcSubnet[]; + selectRouteTableIds(selection?: SubnetSelection): string[]; + + /** + * Return availability zones for the given selection strategy + * + * Requires that at least once subnet is matched, throws a descriptive + * error message otherwise. + */ + selectSubnetAZs(selection?: SubnetSelection): string[]; /** * Return a dependable object representing internet connectivity for the given subnets @@ -220,15 +232,25 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Returns IDs of selected subnets */ - public subnetIds(selection: SubnetSelection = {}): string[] { - selection = reifySelectionDefaults(selection); + public selectSubnetIds(selection: SubnetSelection = {}): string[] { + const subnets = this.selectSubnets(selection); + return subnets.map(s => s.subnetId); + } - const nets = this.subnets(selection); - if (nets.length === 0) { - throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); - } + /** + * Returns IDs of the route tables associated with the selected subnets + */ + public selectRouteTableIds(selection: SubnetSelection = {}): string[] { + const subnets = this.selectSubnets(selection); + return (subnets as VpcSubnet[]).map(s => s.routeTableId); + } - return nets.map(n => n.subnetId); + /** + * Returns the availability zones for the selected subnets + */ + public selectSubnetAZs(selection: SubnetSelection = {}): string[] { + const subnets = this.selectSubnets(selection); + return [...new Set(subnets.map(s => s.availabilityZone))]; } /** @@ -238,7 +260,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { selection = reifySelectionDefaults(selection); const ret = new CompositeDependable(); - for (const subnet of this.subnets(selection)) { + for (const subnet of this.selectSubnets(selection)) { ret.add(subnet.internetConnectivityEstablished); } return ret; @@ -287,27 +309,28 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Return the subnets appropriate for the placement strategy */ - public subnets(selection: SubnetSelection = {}): IVpcSubnet[] { + protected selectSubnets(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); - - // Select by name - if (selection.subnetName !== undefined) { - const allSubnets = this.privateSubnets.concat(this.publicSubnets).concat(this.isolatedSubnets); - const selectedSubnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); - if (selectedSubnets.length === 0) { - throw new Error(`No subnets with name: ${selection.subnetName}`); - } - return selectedSubnets; + let subnets: IVpcSubnet[] = []; + + if (selection.subnetName !== undefined) { // Select by name + const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; + subnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); + } else if (selection.subnetType === undefined) { // No type + subnets = this.privateSubnets; + } else { // Select by type + subnets = { + [SubnetType.Isolated]: this.isolatedSubnets, + [SubnetType.Private]: this.privateSubnets, + [SubnetType.Public]: this.publicSubnets, + }[selection.subnetType]; } - // Select by type - if (selection.subnetType === undefined) { return this.privateSubnets; } + if (subnets.length === 0) { + throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); + } - return { - [SubnetType.Isolated]: this.isolatedSubnets, - [SubnetType.Private]: this.privateSubnets, - [SubnetType.Public]: this.publicSubnets, - }[selection.subnetType]; + return subnets; } } @@ -392,15 +415,15 @@ export interface VpcSubnetImportProps { * Returns "private subnets" by default. */ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { - if (placement.subnetType !== undefined && placement.subnetName !== undefined) { - throw new Error('Only one of subnetType and subnetName can be supplied'); - } + if (placement.subnetType !== undefined && placement.subnetName !== undefined) { + throw new Error('Only one of subnetType and subnetName can be supplied'); + } - if (placement.subnetType === undefined && placement.subnetName === undefined) { - return { subnetType: SubnetType.Private }; - } + if (placement.subnetType === undefined && placement.subnetName === undefined) { + return { subnetType: SubnetType.Private }; + } - return placement; + return placement; } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index cad636b271fff..5170d2e21dd96 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -144,7 +144,7 @@ export interface VpcNetworkProps { * * @default on the route tables associated with private subnets */ - readonly vpnRoutePropagation?: SubnetType[] + readonly vpnRoutePropagation?: SubnetSelection[] /** * Gateway endpoints to add to this VPC. @@ -414,19 +414,10 @@ export class VpcNetwork extends VpcNetworkBase { this.vpnGatewayId = vpnGateway.vpnGatewayName; // Propagate routes on route tables associated with the right subnets - const vpnRoutePropagation = props.vpnRoutePropagation || [SubnetType.Private]; - let subnets: IVpcSubnet[] = []; - if (vpnRoutePropagation.includes(SubnetType.Public)) { - subnets = [...subnets, ...this.publicSubnets]; - } - if (vpnRoutePropagation.includes(SubnetType.Private)) { - subnets = [...subnets, ...this.privateSubnets]; - } - if (vpnRoutePropagation.includes(SubnetType.Isolated)) { - subnets = [...subnets, ...this.isolatedSubnets]; - } + const vpnRoutePropagation = props.vpnRoutePropagation || [{ subnetType: SubnetType.Private }]; + const routeTableIds = [...new Set(Array().concat(...vpnRoutePropagation.map(s => this.selectRouteTableIds(s))))]; const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { - routeTableIds: (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId), + routeTableIds, vpnGatewayId: this.vpnGatewayId }); @@ -514,7 +505,7 @@ export class VpcNetwork extends VpcNetworkBase { let natSubnets: VpcPublicSubnet[]; if (placement) { - const subnets = this.subnets(placement); + const subnets = this.selectSubnets(placement); for (const sub of subnets) { if (this.publicSubnets.indexOf(sub) === -1) { throw new Error(`natGatewayPlacement ${placement} contains non public subnet ${sub}`); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index cfd79a5c13036..0c230281f581c 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -64,8 +64,12 @@ export = { S3: { service: GatewayVpcEndpointAwsService.S3, subnets: [ - { subnetType: SubnetType.Public }, - { subnetType: SubnetType.Private } + { + subnetType: SubnetType.Public + }, + { + subnetType: SubnetType.Private + } ] } } diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 5f2bc2fb68578..f2d0f628ad4a5 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -439,7 +439,11 @@ export = { { subnetType: SubnetType.Isolated, name: 'Isolated' }, ], vpnGateway: true, - vpnRoutePropagation: [SubnetType.Isolated] + vpnRoutePropagation: [ + { + subnetType: SubnetType.Isolated + } + ] }); expect(stack).to(haveResource('AWS::EC2::VPNGatewayRoutePropagation', { @@ -470,8 +474,12 @@ export = { ], vpnGateway: true, vpnRoutePropagation: [ - SubnetType.Private, - SubnetType.Isolated + { + subnetType: SubnetType.Private + }, + { + subnetType: SubnetType.Isolated + } ] }); @@ -601,7 +609,7 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.subnetIds(); + const nets = vpc.selectSubnetIds(); // THEN test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); @@ -614,7 +622,7 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.subnetIds({ subnetType: SubnetType.Public }); + const nets = vpc.selectSubnetIds({ subnetType: SubnetType.Public }); // THEN test.deepEqual(nets, vpc.publicSubnets.map(s => s.subnetId)); @@ -633,7 +641,7 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetType: SubnetType.Isolated }); + const nets = vpc.selectSubnetIds({ subnetType: SubnetType.Isolated }); // THEN test.deepEqual(nets, vpc.isolatedSubnets.map(s => s.subnetId)); @@ -652,7 +660,7 @@ export = { }); // WHEN - const nets = vpc.subnetIds({ subnetName: 'DontTalkToMe' }); + const nets = vpc.selectSubnetIds({ subnetName: 'DontTalkToMe' }); // THEN test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); @@ -669,7 +677,7 @@ export = { }); test.throws(() => { - vpc.subnetIds(); + vpc.selectSubnetIds(); }, /There are no 'Private' subnets in this VPC/); test.done(); @@ -733,7 +741,7 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetType: SubnetType.Isolated }); + const nets = importedVpc.selectSubnetIds({ subnetType: SubnetType.Isolated }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); @@ -756,7 +764,7 @@ export = { }); // WHEN - const nets = importedVpc.subnetIds({ subnetName: isolatedName }); + const nets = importedVpc.selectSubnetIds({ subnetName: isolatedName }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 062312d6cfb89..68add808d9190 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -189,7 +189,7 @@ export abstract class BaseService extends cdk.Construct this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - subnets: vpc.subnetIds(vpcSubnets), + subnets: vpc.selectSubnetIds(vpcSubnets), securityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]).toList(), } }; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 174d3eff33509..38307f39787ae 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -163,7 +163,7 @@ export class Cluster extends ClusterBase { // Get subnetIds for all selected subnets const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.Public }, { subnetType: ec2.SubnetType.Private }]; - const subnetIds = flatMap(placements, p => this.vpc.subnetIds(p)); + const subnetIds = [...new Set(Array().concat(...placements.map(s => props.vpc.selectSubnetIds(s))))]; const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, @@ -332,11 +332,3 @@ class ImportedCluster extends ClusterBase { } } } - -function flatMap(xs: T[], f: (x: T) => U[]): U[] { - const ret = new Array(); - for (const x of xs) { - ret.push(...f(x)); - } - return ret; -} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 2d2791a1a6f9f..705e724b81b94 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -101,7 +101,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const vpcSubnets = ifUndefined(baseProps.vpcSubnets, { subnetType: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private }); - const subnets = baseProps.vpc.subnetIds(vpcSubnets); + const subnets = baseProps.vpc.selectSubnetIds(vpcSubnets); this.vpc = baseProps.vpc; diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 87ad0335cf238..6cd52763b5560 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -544,7 +544,7 @@ export class Function extends FunctionBase { // won't work because the ENIs don't get a Public IP. // Why are we not simply forcing vpcSubnets? Because you might still be choosing // Isolated networks or selecting among 2 sets of Private subnets by name. - const subnetIds = props.vpc.subnetIds(props.vpcSubnets); + const subnetIds = props.vpc.selectSubnetIds(props.vpcSubnets); const publicSubnetIds = new Set(props.vpc.publicSubnets.map(s => s.subnetId)); for (const subnetId of subnetIds) { if (publicSubnetIds.has(subnetId)) { diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 24d547b0262c1..7638c54c0e175 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -235,7 +235,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu this.vpc = props.instanceProps.vpc; this.vpcSubnets = props.instanceProps.vpcSubnets; - const subnetIds = props.instanceProps.vpc.subnetIds(props.instanceProps.vpcSubnets); + const subnetIds = props.instanceProps.vpc.selectSubnetIds(props.instanceProps.vpcSubnets); // Cannot test whether the subnets are in different AZs, but at least we can test the amount. if (subnetIds.length < 2) { diff --git a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts index a892fcf021d7e..b1fd5a0569648 100644 --- a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts @@ -139,7 +139,7 @@ export class RotationSingleUser extends cdk.Construct { vpc: props.vpc }); - const vpcSubnetIds = props.vpc.subnetIds(props.vpcSubnets); + const vpcSubnetIds = props.vpc.selectSubnetIds(props.vpcSubnets); props.target.connections.allowDefaultPortFrom(securityGroup); From f3811d6d9c313db92e22ce8ad3509f5fcfbb4479 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 8 Apr 2019 15:51:09 +0200 Subject: [PATCH 17/22] Reinstate selectSubnets() but return a SOA instead of an AOS Explicitly deal with the fact that not all IVpcSubnets are VpcSubnets. --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 15 +-- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 122 ++++++++++-------- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 11 +- .../aws-ec2/test/test.vpc-endpoint.ts | 21 --- 4 files changed, 83 insertions(+), 86 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 6aa4220102c83..6f89be1c25935 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -154,7 +154,11 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi super(scope, id); const subnets = props.subnets || [{ subnetType: SubnetType.Private }]; - const routeTableIds = [...new Set(Array().concat(...subnets.map(s => props.vpc.selectRouteTableIds(s))))]; + const routeTableIds = [...new Set(Array().concat(...subnets.map(s => props.vpc.selectSubnets(s).routeTableIds)))]; + + if (routeTableIds.length === 0) { + throw new Error(`Can't add an endpoint to VPC; route table IDs are not available`); + } const endpoint = new CfnVPCEndpoint(this, 'Resource', { policyDocument: new cdk.Token(() => this.policyDocument), @@ -387,13 +391,8 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn securityGroups: [securityGroup] }); - const subnetIds = props.vpc.selectSubnetIds(props.subnets); - - const availabilityZones = props.vpc.selectSubnetAZs(props.subnets); - - if (availabilityZones.length !== subnetIds.length) { - throw new Error('Only one subnet per availability zone is allowed.'); - } + const subnets = props.vpc.selectSubnets({ ...props.subnets, onePerAz: true }); + const subnetIds = subnets.subnetIds; const endpoint = new CfnVPCEndpoint(this, 'Resource', { privateDnsEnabled: props.privateDnsEnabled || true, diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 9b2df00fe65ab..4a78984019753 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,6 +1,5 @@ import { Construct, IConstruct, IDependable } from '@aws-cdk/cdk'; import { DEFAULT_SUBNET_NAME, subnetName } from './util'; -import { VpcSubnet } from './vpc'; import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; import { VpnConnection, VpnConnectionOptions } from './vpn'; @@ -20,6 +19,11 @@ export interface IVpcSubnet extends IConstruct { */ readonly internetConnectivityEstablished: IDependable; + /** + * Route table ID + */ + readonly routeTableId?: string; + /** * Exports this subnet to another stack. */ @@ -68,31 +72,17 @@ export interface IVpcNetwork extends IConstruct { * Requires that at least once subnet is matched, throws a descriptive * error message otherwise. * - * Prefer to use this method over {@link subnets} if you need to pass subnet - * IDs to a CloudFormation Resource. + * @deprecated Use selectSubnets() instead. */ selectSubnetIds(selection?: SubnetSelection): string[]; /** - * Return IDs of the route tables appropriate for the given selection strategy - * - * Requires that at least once subnet is matched, throws a descriptive - * error message otherwise. - */ - selectRouteTableIds(selection?: SubnetSelection): string[]; - - /** - * Return availability zones for the given selection strategy + * Return information on the subnets appropriate for the given selection strategy * * Requires that at least once subnet is matched, throws a descriptive * error message otherwise. */ - selectSubnetAZs(selection?: SubnetSelection): string[]; - - /** - * Return a dependable object representing internet connectivity for the given subnets - */ - subnetInternetDependencies(selection?: SubnetSelection): IDependable; + selectSubnets(selection?: SubnetSelection): SelectedSubnets; /** * Return whether all of the given subnets are from the VPC's public subnets. @@ -182,6 +172,38 @@ export interface SubnetSelection { * @default name */ readonly subnetName?: string; + + /** + * If true, return at most one subnet per AZ + * + * @defautl false + */ + readonly onePerAz?: boolean; +} + +/** + * Result of selecting a subset of subnets from a VPC + */ +export interface SelectedSubnets { + /** + * The subnet IDs + */ + readonly subnetIds: string[]; + + /** + * The respective AZs of each subnet + */ + readonly availabilityZones: string[]; + + /** + * Route table IDs of each respective subnet + */ + readonly routeTableIds: string[]; + + /** + * Dependency representing internet connectivity for these subnets + */ + readonly internetConnectedDependency: IDependable; } /** @@ -229,41 +251,22 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { */ public readonly natDependencies = new Array(); - /** - * Returns IDs of selected subnets - */ - public selectSubnetIds(selection: SubnetSelection = {}): string[] { - const subnets = this.selectSubnets(selection); - return subnets.map(s => s.subnetId); - } - - /** - * Returns IDs of the route tables associated with the selected subnets - */ - public selectRouteTableIds(selection: SubnetSelection = {}): string[] { - const subnets = this.selectSubnets(selection); - return (subnets as VpcSubnet[]).map(s => s.routeTableId); - } - - /** - * Returns the availability zones for the selected subnets - */ - public selectSubnetAZs(selection: SubnetSelection = {}): string[] { - const subnets = this.selectSubnets(selection); - return [...new Set(subnets.map(s => s.availabilityZone))]; + public selectSubnetIds(selection?: SubnetSelection): string[] { + return this.selectSubnets(selection).subnetIds; } /** - * Return a dependable object representing internet connectivity for the given subnets + * Returns IDs of selected subnets */ - public subnetInternetDependencies(selection: SubnetSelection = {}): IDependable { - selection = reifySelectionDefaults(selection); + public selectSubnets(selection: SubnetSelection = {}): SelectedSubnets { + const subnets = this.selectSubnetObjects(selection); - const ret = new CompositeDependable(); - for (const subnet of this.selectSubnets(selection)) { - ret.add(subnet.internetConnectivityEstablished); - } - return ret; + return { + subnetIds: subnets.map(s => s.subnetId), + availabilityZones: subnets.map(s => s.availabilityZone), + routeTableIds: subnets.map(s => s.routeTableId).filter(notUndefined), // Possibly don't have this information + internetConnectedDependency: tap(new CompositeDependable(), d => subnets.forEach(s => d.add(s.internetConnectivityEstablished))), + }; } /** @@ -309,21 +312,24 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Return the subnets appropriate for the placement strategy */ - protected selectSubnets(selection: SubnetSelection = {}): IVpcSubnet[] { + protected selectSubnetObjects(selection: SubnetSelection = {}): IVpcSubnet[] { selection = reifySelectionDefaults(selection); let subnets: IVpcSubnet[] = []; if (selection.subnetName !== undefined) { // Select by name const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; subnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); - } else if (selection.subnetType === undefined) { // No type - subnets = this.privateSubnets; } else { // Select by type subnets = { [SubnetType.Isolated]: this.isolatedSubnets, [SubnetType.Private]: this.privateSubnets, [SubnetType.Public]: this.publicSubnets, - }[selection.subnetType]; + }[selection.subnetType || SubnetType.Private]; + + if (selection.onePerAz && subnets.length > 0) { + // Restrict to at most one subnet group + subnets = subnets.filter(s => subnetName(s) === subnetName(subnets[0])); + } } if (subnets.length === 0) { @@ -460,3 +466,15 @@ class CompositeDependable implements IDependable { return ret; } } + +/** + * Invoke a function on a value (for its side effect) and return the value + */ +function tap(x: T, fn: (x: T) => void): T { + fn(x); + return x; +} + +function notUndefined(x: T | undefined): x is T { + return x !== undefined; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 5170d2e21dd96..9ce4f38062d1d 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -415,7 +415,7 @@ export class VpcNetwork extends VpcNetworkBase { // Propagate routes on route tables associated with the right subnets const vpnRoutePropagation = props.vpnRoutePropagation || [{ subnetType: SubnetType.Private }]; - const routeTableIds = [...new Set(Array().concat(...vpnRoutePropagation.map(s => this.selectRouteTableIds(s))))]; + const routeTableIds = [...new Set(Array().concat(...vpnRoutePropagation.map(s => this.selectSubnets(s).routeTableIds)))]; const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { routeTableIds, vpnGatewayId: this.vpnGatewayId @@ -505,7 +505,7 @@ export class VpcNetwork extends VpcNetworkBase { let natSubnets: VpcPublicSubnet[]; if (placement) { - const subnets = this.selectSubnets(placement); + const subnets = this.selectSubnetObjects(placement); for (const sub of subnets) { if (this.publicSubnets.indexOf(sub) === -1) { throw new Error(`natGatewayPlacement ${placement} contains non public subnet ${sub}`); @@ -660,7 +660,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { /** * The routeTableId attached to this subnet. */ - public readonly routeTableId: string; + public readonly routeTableId?: string; private readonly internetDependencies = new ConcreteDependable(); @@ -701,7 +701,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { protected addDefaultRouteToNAT(natGatewayId: string) { const route = new CfnRoute(this, `DefaultRoute`, { - routeTableId: this.routeTableId, + routeTableId: this.routeTableId!, destinationCidrBlock: '0.0.0.0/0', natGatewayId }); @@ -716,7 +716,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { gateway: CfnInternetGateway, gatewayAttachment: CfnVPCGatewayAttachment) { const route = new CfnRoute(this, `DefaultRoute`, { - routeTableId: this.routeTableId, + routeTableId: this.routeTableId!, destinationCidrBlock: '0.0.0.0/0', gatewayId: gateway.ref }); @@ -819,6 +819,7 @@ class ImportedVpcSubnet extends cdk.Construct implements IVpcSubnet { public readonly internetConnectivityEstablished: cdk.IDependable = new cdk.ConcreteDependable(); public readonly availabilityZone: string; public readonly subnetId: string; + public readonly routeTableId?: string = undefined; constructor(scope: cdk.Construct, id: string, private readonly props: VpcSubnetImportProps) { super(scope, id); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index 0c230281f581c..85b3c7a2a5a9f 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -294,27 +294,6 @@ export = { test.done(); }, - 'throws when using more than one subnet per availability zone'(test: Test) { - // GIVEN - const stack = new Stack(); - - // WHEN - const vpc = new VpcNetwork(stack, 'VpcNetwork', { - maxAZs: 1, - subnetConfiguration: [ - {name: 'app', subnetType: SubnetType.Private }, - {name: 'db', subnetType: SubnetType.Private }, - ] - }); - - // THEN - test.throws(() => vpc.addInterfaceEndpoint('Bad', { - service: InterfaceVpcEndpointAwsService.SecretsManager - }), /availability zone/); - - test.done(); - }, - 'import/export'(test: Test) { // GIVEN const stack1 = new Stack(); From 394cd4edf6538374a28b757fc2d63a7d2d2ce106 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 8 Apr 2019 17:05:38 +0200 Subject: [PATCH 18/22] Fix calls to subnetInternetDependencies --- .../aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 705e724b81b94..73b0494f0dae8 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -113,7 +113,7 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. ...additionalProps }); if (internetFacing) { - resource.node.addDependency(baseProps.vpc.subnetInternetDependencies(vpcSubnets)); + resource.node.addDependency(baseProps.vpc.selectSubnets(vpcSubnets).internetConnectedDependency); } if (baseProps.deletionProtection) { this.setAttribute('deletion_protection.enabled', 'true'); } diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index b2dc9cd677398..9340acf08c375 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -308,7 +308,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu } // Get the actual subnet objects so we can depend on internet connectivity. - const internetConnected = props.instanceProps.vpc.subnetInternetDependencies(props.instanceProps.vpcSubnets); + const internetConnected = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).internetConnectedDependency; for (let i = 0; i < instanceCount; i++) { const instanceIndex = i + 1; From c0fcd02bca90e7ded90ecdbfd5448217c6c3f8bd Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 8 Apr 2019 17:49:01 +0200 Subject: [PATCH 19/22] Error message --- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 6f89be1c25935..416339ee1a4b9 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -157,7 +157,7 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi const routeTableIds = [...new Set(Array().concat(...subnets.map(s => props.vpc.selectSubnets(s).routeTableIds)))]; if (routeTableIds.length === 0) { - throw new Error(`Can't add an endpoint to VPC; route table IDs are not available`); + throw new Error(`Can't add a gateway endpoint to VPC; route table IDs are not available`); } const endpoint = new CfnVPCEndpoint(this, 'Resource', { From 6651dae8d86a10aa98728e5d23b9176da08565d4 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 8 Apr 2019 17:49:28 +0200 Subject: [PATCH 20/22] Fix onePerAz --- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 4 ++-- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 4a78984019753..a550a9a8be10c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -426,7 +426,7 @@ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { } if (placement.subnetType === undefined && placement.subnetName === undefined) { - return { subnetType: SubnetType.Private }; + return { subnetType: SubnetType.Private, onePerAz: placement.onePerAz }; } return placement; @@ -477,4 +477,4 @@ function tap(x: T, fn: (x: T) => void): T { function notUndefined(x: T | undefined): x is T { return x !== undefined; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index f2d0f628ad4a5..0b787e4bd022e 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -680,6 +680,26 @@ export = { vpc.selectSubnetIds(); }, /There are no 'Private' subnets in this VPC/); + test.done(); + }, + + 'select subnets with az restriction'(test: Test) { + // GIVEN + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork', { + maxAZs: 1, + subnetConfiguration: [ + {name: 'app', subnetType: SubnetType.Private }, + {name: 'db', subnetType: SubnetType.Private }, + ] + }); + + // WHEN + const nets = vpc.selectSubnets({ onePerAz: true }); + + // THEN + test.deepEqual(nets.subnetIds.length, 1); + test.deepEqual(nets.subnetIds[0], vpc.privateSubnets[0].subnetId); test.done(); } }, From 5f883b8c30f0b906306ddda2a1acaacff9a46b72 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 8 Apr 2019 19:45:04 +0200 Subject: [PATCH 21/22] Add test for gateway endpoint with imported VPC --- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 4 ++-- .../@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index a550a9a8be10c..87dadb2ee7870 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -69,7 +69,7 @@ export interface IVpcNetwork extends IConstruct { /** * Return IDs of the subnets appropriate for the given selection strategy * - * Requires that at least once subnet is matched, throws a descriptive + * Requires that at least one subnet is matched, throws a descriptive * error message otherwise. * * @deprecated Use selectSubnets() instead. @@ -79,7 +79,7 @@ export interface IVpcNetwork extends IConstruct { /** * Return information on the subnets appropriate for the given selection strategy * - * Requires that at least once subnet is matched, throws a descriptive + * Requires that at least one subnet is matched, throws a descriptive * error message otherwise. */ selectSubnets(selection?: SubnetSelection): SelectedSubnets; diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index 85b3c7a2a5a9f..6de8f2f83f38f 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -229,6 +229,24 @@ export = { }, })); + test.done(); + }, + + 'throws with an imported vpc'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = VpcNetwork.import(stack, 'VPC', { + vpcId: 'id', + privateSubnetIds: ['1', '2', '3'], + availabilityZones: ['a', 'b', 'c'] + }); + + // THEN + test.throws(() => new GatewayVpcEndpoint(stack, 'Gateway', { + service: GatewayVpcEndpointAwsService.S3, + vpc + }), /route table/); + test.done(); } }, From f83acd96e387f349d3bd89e8161805488f658b7c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 8 Apr 2019 19:59:41 +0200 Subject: [PATCH 22/22] Remove calls to selectSubnetIds --- .../aws-autoscaling/lib/auto-scaling-group.ts | 2 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 2 +- packages/@aws-cdk/aws-ec2/test/test.vpc.ts | 32 +++++++++---------- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- packages/@aws-cdk/aws-eks/lib/cluster.ts | 2 +- .../lib/shared/base-load-balancer.ts | 6 ++-- packages/@aws-cdk/aws-lambda/lib/function.ts | 2 +- packages/@aws-cdk/aws-rds/lib/cluster.ts | 2 +- .../aws-rds/lib/rotation-single-user.ts | 4 +-- 9 files changed, 27 insertions(+), 27 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 e355678772760..efc1209770f8b 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -268,7 +268,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`); } - const subnetIds = props.vpc.selectSubnetIds(props.vpcSubnets); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, minSize: minCapacity.toString(), diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index ffdeefa6c266f..e178ffd3287c9 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -901,7 +901,7 @@ export class Project extends ProjectBase { .addAction('ec2:CreateNetworkInterfacePermission')); return { vpcId: props.vpc.vpcId, - subnets: props.vpc.selectSubnetIds(props.subnetSelection), + subnets: props.vpc.selectSubnets(props.subnetSelection).subnetIds, securityGroupIds: this._securityGroups.map(s => s.securityGroupId) }; } diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 0b787e4bd022e..4b384638a46f9 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -609,10 +609,10 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.selectSubnetIds(); + const { subnetIds } = vpc.selectSubnets(); // THEN - test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); + test.deepEqual(subnetIds, vpc.privateSubnets.map(s => s.subnetId)); test.done(); }, @@ -622,10 +622,10 @@ export = { const vpc = new VpcNetwork(stack, 'VPC'); // WHEN - const nets = vpc.selectSubnetIds({ subnetType: SubnetType.Public }); + const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.Public }); // THEN - test.deepEqual(nets, vpc.publicSubnets.map(s => s.subnetId)); + test.deepEqual(subnetIds, vpc.publicSubnets.map(s => s.subnetId)); test.done(); }, @@ -641,10 +641,10 @@ export = { }); // WHEN - const nets = vpc.selectSubnetIds({ subnetType: SubnetType.Isolated }); + const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.Isolated }); // THEN - test.deepEqual(nets, vpc.isolatedSubnets.map(s => s.subnetId)); + test.deepEqual(subnetIds, vpc.isolatedSubnets.map(s => s.subnetId)); test.done(); }, @@ -660,10 +660,10 @@ export = { }); // WHEN - const nets = vpc.selectSubnetIds({ subnetName: 'DontTalkToMe' }); + const { subnetIds } = vpc.selectSubnets({ subnetName: 'DontTalkToMe' }); // THEN - test.deepEqual(nets, vpc.privateSubnets.map(s => s.subnetId)); + test.deepEqual(subnetIds, vpc.privateSubnets.map(s => s.subnetId)); test.done(); }, @@ -677,7 +677,7 @@ export = { }); test.throws(() => { - vpc.selectSubnetIds(); + vpc.selectSubnets(); }, /There are no 'Private' subnets in this VPC/); test.done(); @@ -695,11 +695,11 @@ export = { }); // WHEN - const nets = vpc.selectSubnets({ onePerAz: true }); + const { subnetIds } = vpc.selectSubnets({ onePerAz: true }); // THEN - test.deepEqual(nets.subnetIds.length, 1); - test.deepEqual(nets.subnetIds[0], vpc.privateSubnets[0].subnetId); + test.deepEqual(subnetIds.length, 1); + test.deepEqual(subnetIds[0], vpc.privateSubnets[0].subnetId); test.done(); } }, @@ -761,11 +761,11 @@ export = { }); // WHEN - const nets = importedVpc.selectSubnetIds({ subnetType: SubnetType.Isolated }); + const { subnetIds } = importedVpc.selectSubnets({ subnetType: SubnetType.Isolated }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets.map(s => s.subnetId)); + test.deepEqual(subnetIds, importedVpc.isolatedSubnets.map(s => s.subnetId)); test.done(); }, @@ -784,11 +784,11 @@ export = { }); // WHEN - const nets = importedVpc.selectSubnetIds({ subnetName: isolatedName }); + const { subnetIds } = importedVpc.selectSubnets({ subnetName: isolatedName }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets.map(s => s.subnetId)); + test.deepEqual(subnetIds, importedVpc.isolatedSubnets.map(s => s.subnetId)); } test.done(); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 040f65083f703..be6b0df0cccc1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -234,7 +234,7 @@ export abstract class BaseService extends cdk.Construct this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - subnets: vpc.selectSubnetIds(vpcSubnets), + subnets: vpc.selectSubnets(vpcSubnets).subnetIds, securityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]).toList(), } }; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 38307f39787ae..8dcdd35f767f0 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -163,7 +163,7 @@ export class Cluster extends ClusterBase { // Get subnetIds for all selected subnets const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.Public }, { subnetType: ec2.SubnetType.Private }]; - const subnetIds = [...new Set(Array().concat(...placements.map(s => props.vpc.selectSubnetIds(s))))]; + const subnetIds = [...new Set(Array().concat(...placements.map(s => props.vpc.selectSubnets(s).subnetIds)))]; const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 73b0494f0dae8..5d3e73bb723ee 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -101,19 +101,19 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const vpcSubnets = ifUndefined(baseProps.vpcSubnets, { subnetType: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private }); - const subnets = baseProps.vpc.selectSubnetIds(vpcSubnets); + const { subnetIds, internetConnectedDependency } = baseProps.vpc.selectSubnets(vpcSubnets); this.vpc = baseProps.vpc; const resource = new CfnLoadBalancer(this, 'Resource', { name: baseProps.loadBalancerName, - subnets, + subnets: subnetIds, scheme: internetFacing ? 'internet-facing' : 'internal', loadBalancerAttributes: new cdk.Token(() => renderAttributes(this.attributes)), ...additionalProps }); if (internetFacing) { - resource.node.addDependency(baseProps.vpc.selectSubnets(vpcSubnets).internetConnectedDependency); + resource.node.addDependency(internetConnectedDependency); } if (baseProps.deletionProtection) { this.setAttribute('deletion_protection.enabled', 'true'); } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 50ae12926ca54..324783d9605e8 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -550,7 +550,7 @@ export class Function extends FunctionBase { // won't work because the ENIs don't get a Public IP. // Why are we not simply forcing vpcSubnets? Because you might still be choosing // Isolated networks or selecting among 2 sets of Private subnets by name. - const subnetIds = props.vpc.selectSubnetIds(props.vpcSubnets); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); const publicSubnetIds = new Set(props.vpc.publicSubnets.map(s => s.subnetId)); for (const subnetId of subnetIds) { if (publicSubnetIds.has(subnetId)) { diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 9340acf08c375..13b81137ad220 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -235,7 +235,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu this.vpc = props.instanceProps.vpc; this.vpcSubnets = props.instanceProps.vpcSubnets; - const subnetIds = props.instanceProps.vpc.selectSubnetIds(props.instanceProps.vpcSubnets); + const { subnetIds } = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets); // Cannot test whether the subnets are in different AZs, but at least we can test the amount. if (subnetIds.length < 2) { diff --git a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts index b1fd5a0569648..357e9504a0fd2 100644 --- a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts +++ b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts @@ -139,7 +139,7 @@ export class RotationSingleUser extends cdk.Construct { vpc: props.vpc }); - const vpcSubnetIds = props.vpc.selectSubnetIds(props.vpcSubnets); + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); props.target.connections.allowDefaultPortFrom(securityGroup); @@ -149,7 +149,7 @@ export class RotationSingleUser extends cdk.Construct { endpoint: `https://secretsmanager.${this.node.stack.region}.${this.node.stack.urlSuffix}`, functionName: rotationFunctionName, vpcSecurityGroupIds: securityGroup.securityGroupId, - vpcSubnetIds: vpcSubnetIds.join(',') + vpcSubnetIds: subnetIds.join(',') } });