diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index fb8b7df94acd4..e190d03b8ee2e 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -582,6 +582,42 @@ OPTIONS added to them. See [#906](https://github.com/aws/aws-cdk/issues/906) for a list of CORS features which are not yet supported. +### Endpoint Configuration +API gateway allows you to specify an +[API Endpoint Type](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html). +To define an endpoint type for the API gateway, use `endpointConfiguration` property: + +```ts +const api = new apigw.RestApi(stack, 'api', { + endpointConfiguration: { + types: [ apigw.EndpointType.EDGE ] + } +}); +``` + +You can also create an association between your Rest API and a VPC endpoint. By doing so, +API Gateway will generate a new +Route53 Alias DNS record which you can use to invoke your private APIs. More info can be found +[here](https://docs.aws.amazon.com/apigateway/latest/developerguide/associate-private-api-with-vpc-endpoint.html). + +Here is an example: + +```ts +const someEndpoint: IVpcEndpoint = /* Get or Create endpoint here */ +const api = new apigw.RestApi(stack, 'api', { + endpointConfiguration: { + types: [ apigw.EndpointType.PRIVATE ], + vpcEndpoints: [ someEndpoint ] + } +}); +``` + +By performing this association, we can invoke the API gateway using the following format: + +``` +https://{rest-api-id}-{vpce-id}.execute-api.{region}.amazonaws.com/{stage} +``` + ## APIGateway v2 APIGateway v2 APIs are now moved to its own package named `aws-apigatewayv2`. For backwards compatibility, existing diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 79b3a640d0023..512911060b553 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,3 +1,4 @@ +import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { CfnOutput, Construct, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; import { ApiKey, IApiKey } from './api-key'; @@ -92,6 +93,23 @@ export interface RestApiProps extends ResourceOptions { */ readonly description?: string; + /** + * The EndpointConfiguration property type specifies the endpoint types of a REST API + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-restapi-endpointconfiguration.html + * + * @default - No endpoint configuration + */ + readonly endpointConfiguration?: EndpointConfiguration; + + /** + * A list of the endpoint types of the API. Use this property when creating + * an API. + * + * @default - No endpoint types. + * @deprecated this property is deprecated, use endpointConfiguration instead + */ + readonly endpointTypes?: EndpointType[]; + /** * The source of the API key for metering requests according to a usage * plan. @@ -108,14 +126,6 @@ export interface RestApiProps extends ResourceOptions { */ readonly binaryMediaTypes?: string[]; - /** - * A list of the endpoint types of the API. Use this property when creating - * an API. - * - * @default - No endpoint types. - */ - readonly endpointTypes?: EndpointType[]; - /** * Indicates whether to roll back the resource if a warning occurs while API * Gateway is creating the RestApi resource. @@ -232,7 +242,7 @@ export class RestApi extends Resource implements IRestApi { failOnWarnings: props.failOnWarnings, minimumCompressionSize: props.minimumCompressionSize, binaryMediaTypes: props.binaryMediaTypes, - endpointConfiguration: props.endpointTypes ? { types: props.endpointTypes } : undefined, + endpointConfiguration: this.configureEndpoints(props), apiKeySourceType: props.apiKeySourceType, cloneFrom: props.cloneFrom ? props.cloneFrom.restApiId : undefined, parameters: props.parameters @@ -430,6 +440,43 @@ export class RestApi extends Resource implements IRestApi { resource.node.addDependency(apiResource); } + + private configureEndpoints(props: RestApiProps): CfnRestApi.EndpointConfigurationProperty | undefined { + if (props.endpointTypes && props.endpointConfiguration) { + throw new Error('Only one of the RestApi props, endpointTypes or endpointConfiguration, is allowed'); + } + if (props.endpointConfiguration) { + return { + types: props.endpointConfiguration.types, + vpcEndpointIds: props.endpointConfiguration?.vpcEndpoints?.map(vpcEndpoint => vpcEndpoint.vpcEndpointId) + }; + } + if (props.endpointTypes) { + return { types: props.endpointTypes }; + } + return undefined; + } +} + +/** + * The endpoint configuration of a REST API, including VPCs and endpoint types. + * + * EndpointConfiguration is a property of the AWS::ApiGateway::RestApi resource. + */ +export interface EndpointConfiguration { + /** + * A list of endpoint types of an API or its custom domain name. + * + * @default - no endpoint types. + */ + readonly types: EndpointType[]; + + /** + * A list of VPC Endpoints against which to create Route53 ALIASes + * + * @default - no ALIASes are created for the endpoint. + */ + readonly vpcEndpoints?: IVpcEndpoint[]; } export enum ApiKeySourceType { diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index bbd777d2c7abc..aa80ab899522f 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -63,7 +63,6 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "1.24.0", - "@aws-cdk/aws-ec2": "1.24.0", "@types/nodeunit": "^0.0.30", "cdk-build-tools": "1.24.0", "cdk-integ-tools": "1.24.0", @@ -73,6 +72,7 @@ }, "dependencies": { "@aws-cdk/aws-certificatemanager": "1.24.0", + "@aws-cdk/aws-ec2": "1.24.0", "@aws-cdk/aws-elasticloadbalancingv2": "1.24.0", "@aws-cdk/aws-iam": "1.24.0", "@aws-cdk/aws-lambda": "1.24.0", @@ -81,6 +81,7 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-certificatemanager": "1.24.0", + "@aws-cdk/aws-ec2": "1.24.0", "@aws-cdk/aws-elasticloadbalancingv2": "1.24.0", "@aws-cdk/aws-iam": "1.24.0", "@aws-cdk/aws-lambda": "1.24.0", diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json new file mode 100644 index 0000000000000..0a2769170552e --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.expected.json @@ -0,0 +1,751 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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", + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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", + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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", + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPublicSubnet3NATGatewayD4B50EBE": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet3EIPC5ACADAB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + }, + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/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": "test-apigateway-vpcendpoint/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "MyVpcMyVpcEndpointSecurityGroup52BE62B3": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "test-apigateway-vpcendpoint/MyVpc/MyVpcEndpoint/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "MyVpcF9F0CA6F", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "MyVpcF9F0CA6F", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "test-apigateway-vpcendpoint/MyVpc" + } + ], + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + } + } + }, + "MyVpcMyVpcEndpointA401F313": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".execute-api" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "MyVpcMyVpcEndpointSecurityGroup52BE62B3", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + }, + { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + }, + { + "Ref": "MyVpcPrivateSubnet3Subnet772D6AD7" + } + ], + "VpcEndpointType": "Interface" + } + }, + "MyApi49610EDF": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + { + "Ref": "MyVpcMyVpcEndpointA401F313" + } + ] + }, + "Name": "MyApi", + "Policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*" + } + ], + "Version": "2012-10-17" + } + } + }, + "MyApiDeploymentECB0D05E4316cb89aa1d468052daa78055609f5a": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi49610EDF" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "MyApiGETD0C7AA0C" + ] + }, + "MyApiDeploymentStageprodE1054AF0": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "MyApi49610EDF" + }, + "DeploymentId": { + "Ref": "MyApiDeploymentECB0D05E4316cb89aa1d468052daa78055609f5a" + }, + "StageName": "prod" + } + }, + "MyApiCloudWatchRole2BEC1A9C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "MyApiAccount13882D84": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "MyApiCloudWatchRole2BEC1A9C", + "Arn" + ] + } + }, + "DependsOn": [ + "MyApi49610EDF" + ] + }, + "MyApiGETD0C7AA0C": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Fn::GetAtt": [ + "MyApi49610EDF", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "MyApi49610EDF" + }, + "AuthorizationType": "NONE", + "Integration": { + "Type": "MOCK" + } + } + } + }, + "Outputs": { + "MyApiEndpoint869ABE96": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "MyApi49610EDF" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "MyApiDeploymentStageprodE1054AF0" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.ts new file mode 100644 index 0000000000000..e97aaac426188 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.vpc-endpoint.ts @@ -0,0 +1,44 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import * as apigateway from '../lib'; + +/* + * Stack verification steps: + * * curl https://-.execute-api.us-east-1.amazonaws.com/prod/ + * The above command must be executed in the vpc + */ +class Test extends cdk.Stack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const vpc = new ec2.Vpc(this, 'MyVpc', {}); + + const vpcEndpoint = vpc.addInterfaceEndpoint('MyVpcEndpoint', { + service: ec2.InterfaceVpcEndpointAwsService.APIGATEWAY + }); + + const api = new apigateway.RestApi(this, 'MyApi', { + endpointConfiguration: { + types: [ apigateway.EndpointType.PRIVATE ], + vpcEndpoints: [ vpcEndpoint ] + }, + policy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + principals: [new iam.AnyPrincipal()], + actions: ['execute-api:Invoke'], + effect: iam.Effect.ALLOW, + }), + ] + }) + }); + api.root.addMethod('GET'); + } +} + +const app = new cdk.App(); + +new Test(app, 'test-apigateway-vpcendpoint'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index e15ba7949eed3..e8372487184c4 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -1,4 +1,5 @@ import { expect, haveResource, haveResourceLike, ResourcePart, SynthUtils } from '@aws-cdk/assert'; +import { GatewayVpcEndpoint } from '@aws-cdk/aws-ec2'; import { App, CfnElement, CfnResource, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as apigw from '../lib'; @@ -474,15 +475,85 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::RestApi', { EndpointConfiguration: { - Types: [ - "EDGE", - "PRIVATE" - ] + Types: [ + "EDGE", + "PRIVATE" + ] } })); test.done(); }, + '"endpointConfiguration" can be used to specify endpoint types for the api'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const api = new apigw.RestApi(stack, 'api', { + endpointConfiguration: { + types: [ apigw.EndpointType.EDGE, apigw.EndpointType.PRIVATE ] + } + }); + + api.root.addMethod('GET'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::RestApi', { + EndpointConfiguration: { + Types: [ "EDGE", "PRIVATE" ] + } + })); + test.done(); + }, + + '"endpointConfiguration" can be used to specify vpc endpoints on the API'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const api = new apigw.RestApi(stack, 'api', { + endpointConfiguration: { + types: [ apigw.EndpointType.EDGE, apigw.EndpointType.PRIVATE ], + vpcEndpoints: [ + GatewayVpcEndpoint.fromGatewayVpcEndpointId(stack, 'ImportedEndpoint', 'vpcEndpoint'), + GatewayVpcEndpoint.fromGatewayVpcEndpointId(stack, 'ImportedEndpoint2', 'vpcEndpoint2'), + ] + } + }); + + api.root.addMethod('GET'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::RestApi', { + EndpointConfiguration: { + Types: [ + "EDGE", + "PRIVATE" + ], + VpcEndpointIds: [ + "vpcEndpoint", + "vpcEndpoint2" + ] + } + })); + test.done(); + }, + + '"endpointTypes" and "endpointConfiguration" can NOT both be used to specify endpoint configuration for the api'(test: Test) { + // GIVEN + const stack = new Stack(); + + // THEN + test.throws(() => new apigw.RestApi(stack, 'api', { + endpointConfiguration: { + types: [ apigw.EndpointType.PRIVATE ], + vpcEndpoints: [ GatewayVpcEndpoint.fromGatewayVpcEndpointId(stack, 'ImportedEndpoint', 'vpcEndpoint')] + }, + endpointTypes: [ apigw.EndpointType.PRIVATE ] + }), /Only one of the RestApi props, endpointTypes or endpointConfiguration, is allowed/); + test.done(); + }, + '"cloneFrom" can be used to clone an existing API'(test: Test) { // GIVEN const stack = new Stack();