diff --git a/packages/@aws-cdk/aws-elasticsearch/README.md b/packages/@aws-cdk/aws-elasticsearch/README.md index 359b28dda1b66..683f64fd5c9ea 100644 --- a/packages/@aws-cdk/aws-elasticsearch/README.md +++ b/packages/@aws-cdk/aws-elasticsearch/README.md @@ -6,7 +6,7 @@ Features | Stability -----------------------------------|---------------------------------------------------------------- CFN Resources | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) -Higher level constructs for Domain | ![Experimental](https://img.shields.io/badge/experimental-important.svg?style=for-the-badge) +Higher level constructs for Domain | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) > **CFN Resources:** All classes with the `Cfn` prefix in this module ([CFN Resources]) are always > stable and safe to use. @@ -15,11 +15,8 @@ Higher level constructs for Domain | ![Experimental](https://img.shields.io/badg -> **Experimental:** Higher level constructs in this module that are marked as experimental are -> under active development. They are subject to non-backward compatible changes or removal in any -> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and -> breaking changes will be announced in the release notes. This means that while you may use them, -> you may need to update your source code when upgrading to a newer version of this package. +> **Stable:** Higher level constructs in this module that are marked stable will not undergo any +> breaking changes. They will strictly follow the [Semantic Versioning](https://semver.org/) model. --- @@ -146,6 +143,33 @@ This sets up the domain with node to node encryption and encryption at rest. You can also choose to supply your own KMS key to use for encryption at rest. +## VPC Support + +Elasticsearch domains can be placed inside a VPC, providing a secure communication between Amazon ES and other services within the VPC without the need for an internet gateway, NAT device, or VPN connection. + +> Visit [VPC Support for Amazon Elasticsearch Service Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html) for more details. + +```ts +const vpc = new ec2.Vpc(this, 'Vpc'); +const domainProps: es.DomainProps = { + version: es.ElasticsearchVersion.V7_1, + removalPolicy: RemovalPolicy.DESTROY, + vpc, + // must be enabled since our VPC contains multiple private subnets. + zoneAwareness: { + enabled: true, + }, + capacity: { + // must be an even number since the default az count is 2. + dataNodes: 2, + }, +}; +new es.Domain(this, 'Domain', domainProps); +``` + +In addition, you can use the `vpcSubnets` property to control which specific subnets will be used, and the `securityGroups` property to control +which security groups will be attached to the domain. By default, CDK will select all *private* subnets in the VPC, and create one dedicated security group. + ## Metrics Helper methods exist to access common domain metrics for example: diff --git a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts index 74a4dff64f757..a995b7394fe7d 100644 --- a/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts +++ b/packages/@aws-cdk/aws-elasticsearch/lib/domain.ts @@ -331,32 +331,6 @@ export interface CognitoOptions { readonly userPoolId: string; } -/** - * The virtual private cloud (VPC) configuration for the Amazon ES domain. For - * more information, see [VPC Support for Amazon Elasticsearch Service - * Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html) - * in the Amazon Elasticsearch Service Developer Guide. - */ -export interface VpcOptions { - /** - * The list of security groups that are associated with the VPC endpoints - * for the domain. If you don't provide a security group ID, Amazon ES uses - * the default security group for the VPC. To learn more, see [Security Groups for your VPC] - * (https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html) in the Amazon VPC - * User Guide. - */ - readonly securityGroups: ec2.ISecurityGroup[]; - - /** - * Provide one subnet for each Availability Zone that your domain uses. For - * example, you must specify three subnet IDs for a three Availability Zone - * domain. To learn more, see [VPCs and Subnets] - * (https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html) in the - * Amazon VPC User Guide. - */ - readonly subnets: ec2.ISubnet[]; -} - /** * The minimum TLS version required for traffic to the domain. */ @@ -513,14 +487,35 @@ export interface DomainProps { readonly automatedSnapshotStartHour?: number; /** - * The virtual private cloud (VPC) configuration for the Amazon ES domain. For - * more information, see [VPC Support for Amazon Elasticsearch Service - * Domains](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html) - * in the Amazon Elasticsearch Service Developer Guide. + * Place the domain inside this VPC. + * + * @see https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html + * @default - Domain is not placed in a VPC. + */ + readonly vpc?: ec2.IVpc; + + /** + * The list of security groups that are associated with the VPC endpoints + * for the domain. + * + * Only used if `vpc` is specified. * - * @default - VPC not used + * @see https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html + * @default - One new security group is created. */ - readonly vpcOptions?: VpcOptions; + readonly securityGroups?: ec2.ISecurityGroup[]; + + /** + * The specific vpc subnets the domain will be placed in. You must provide one subnet for each Availability Zone + * that your domain uses. For example, you must specify three subnet IDs for a three Availability Zone + * domain. + * + * Only used if `vpc` is specified. + * + * @see https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html + * @default - All private subnets. + */ + readonly vpcSubnets?: ec2.SubnetSelection[]; /** * True to require that all traffic to the domain arrive over HTTPS. @@ -719,7 +714,7 @@ export interface IDomain extends cdk.IResource { * * @default maximum over 1 minute */ - metricClusterIndexWriteBlocked(props?: MetricOptions): Metric; + metricClusterIndexWritesBlocked(props?: MetricOptions): Metric; /** * Metric for the number of nodes. @@ -1002,8 +997,8 @@ abstract class DomainBase extends cdk.Resource implements IDomain { * * @default maximum over 1 minute */ - public metricClusterIndexWriteBlocked(props?: MetricOptions): Metric { - return this.metric('ClusterIndexWriteBlocked', { + public metricClusterIndexWritesBlocked(props?: MetricOptions): Metric { + return this.metric('ClusterIndexWritesBlocked', { statistic: Statistic.MAXIMUM, period: cdk.Duration.minutes(1), ...props, @@ -1177,7 +1172,7 @@ export interface DomainAttributes { /** * Provides an Elasticsearch domain. */ -export class Domain extends DomainBase implements IDomain { +export class Domain extends DomainBase implements IDomain, ec2.IConnectable { /** * Creates a Domain construct that represents an external domain via domain endpoint. * @@ -1264,6 +1259,8 @@ export class Domain extends DomainBase implements IDomain { private readonly domain: CfnDomain; + private readonly _connections: ec2.Connections | undefined; + constructor(scope: Construct, id: string, props: DomainProps) { super(scope, id, { physicalName: props.domainName, @@ -1300,11 +1297,23 @@ export class Domain extends DomainBase implements IDomain { props.zoneAwareness?.enabled ?? props.zoneAwareness?.availabilityZoneCount != null; + + let securityGroups: ec2.ISecurityGroup[] | undefined; + let subnets: ec2.ISubnet[] | undefined; + + if (props.vpc) { + subnets = selectSubnets(props.vpc, props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PRIVATE }]); + securityGroups = props.securityGroups ?? [new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc, + description: `Security group for domain ${this.node.id}`, + })]; + this._connections = new ec2.Connections({ securityGroups }); + } + // If VPC options are supplied ensure that the number of subnets matches the number AZ - if (props.vpcOptions != null && zoneAwarenessEnabled && - new Set(props.vpcOptions?.subnets.map((subnet) => subnet.availabilityZone)).size < availabilityZoneCount) { + if (subnets && zoneAwarenessEnabled && new Set(subnets.map((subnet) => subnet.availabilityZone)).size < availabilityZoneCount) { throw new Error('When providing vpc options you need to provide a subnet for each AZ you are using'); - }; + } if ([dedicatedMasterType, instanceType, warmType].some(t => !t.endsWith('.elasticsearch'))) { throw new Error('Master, data and UltraWarm node instance types must end with ".elasticsearch".'); @@ -1491,10 +1500,11 @@ export class Domain extends DomainBase implements IDomain { } let cfnVpcOptions: CfnDomain.VPCOptionsProperty | undefined; - if (props.vpcOptions) { + + if (securityGroups && subnets) { cfnVpcOptions = { - securityGroupIds: props.vpcOptions.securityGroups.map((sg) => sg.securityGroupId), - subnetIds: props.vpcOptions.subnets.map((subnet) => subnet.subnetId), + securityGroupIds: securityGroups.map((sg) => sg.securityGroupId), + subnetIds: subnets.map((subnet) => subnet.subnetId), }; } @@ -1730,6 +1740,17 @@ export class Domain extends DomainBase implements IDomain { accessPolicy.node.addDependency(this.domain); } } + + /** + * Manages network connections to the domain. This will throw an error in case the domain + * is not placed inside a VPC. + */ + public get connections(): ec2.Connections { + if (!this._connections) { + throw new Error("Connections are only available on VPC enabled domains. Use the 'vpc' property to place a domain inside a VPC"); + } + return this._connections; + } } /** @@ -1778,3 +1799,11 @@ function parseVersion(version: ElasticsearchVersion): number { throw new Error(`Invalid Elasticsearch version: ${versionStr}. Version string needs to start with major and minor version (x.y).`); } } + +function selectSubnets(vpc: ec2.IVpc, vpcSubnets: ec2.SubnetSelection[]): ec2.ISubnet[] { + const selected = []; + for (const selection of vpcSubnets) { + selected.push(...vpc.selectSubnets(selection).subnets); + } + return selected; +} diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index eff2725fb8d1e..755bba611d3d0 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -107,12 +107,12 @@ "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, - "stability": "experimental", - "maturity": "experimental", + "stability": "stable", + "maturity": "stable", "features": [ { "name": "Higher level constructs for Domain", - "stability": "Experimental" + "stability": "Stable" } ], "awscdkio": { diff --git a/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts b/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts index 12810832c0da8..205f73aee7e1b 100644 --- a/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts +++ b/packages/@aws-cdk/aws-elasticsearch/test/domain.test.ts @@ -3,7 +3,7 @@ import '@aws-cdk/assert/jest'; import * as assert from '@aws-cdk/assert'; import * as acm from '@aws-cdk/aws-certificatemanager'; import { Metric, Statistic } from '@aws-cdk/aws-cloudwatch'; -import { Subnet, Vpc, EbsDeviceVolumeType } from '@aws-cdk/aws-ec2'; +import { Vpc, EbsDeviceVolumeType, SecurityGroup } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as logs from '@aws-cdk/aws-logs'; @@ -30,6 +30,84 @@ const readWriteActions = [ ...writeActions, ]; +test('connections throws if domain is placed inside a vpc', () => { + + expect(() => { + new Domain(stack, 'Domain', { + version: ElasticsearchVersion.V7_1, + }).connections; + }).toThrowError("Connections are only available on VPC enabled domains. Use the 'vpc' property to place a domain inside a VPC"); +}); + +test('subnets and security groups can be provided when vpc is used', () => { + + const vpc = new Vpc(stack, 'Vpc'); + const securityGroup = new SecurityGroup(stack, 'CustomSecurityGroup', { + vpc, + }); + const domain = new Domain(stack, 'Domain', { + version: ElasticsearchVersion.V7_9, + vpc, + vpcSubnets: [{ subnets: [vpc.privateSubnets[0]] }], + securityGroups: [securityGroup], + }); + + expect(domain.connections.securityGroups[0].securityGroupId).toEqual(securityGroup.securityGroupId); + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + VPCOptions: { + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'CustomSecurityGroupE5E500E5', + 'GroupId', + ], + }, + ], + SubnetIds: [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + ], + }, + }); + +}); + +test('default subnets and security group when vpc is used', () => { + + const vpc = new Vpc(stack, 'Vpc'); + const domain = new Domain(stack, 'Domain', { + version: ElasticsearchVersion.V7_9, + vpc, + }); + + expect(stack.resolve(domain.connections.securityGroups[0].securityGroupId)).toEqual({ 'Fn::GetAtt': ['DomainSecurityGroup48AA5FD6', 'GroupId'] }); + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + VPCOptions: { + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'DomainSecurityGroup48AA5FD6', + 'GroupId', + ], + }, + ], + SubnetIds: [ + { + Ref: 'VpcPrivateSubnet1Subnet536B997A', + }, + { + Ref: 'VpcPrivateSubnet2Subnet3788AAA1', + }, + { + Ref: 'VpcPrivateSubnet3SubnetF258B56E', + }, + ], + }, + }); + +}); + test('default removalpolicy is retain', () => { new Domain(stack, 'Domain', { version: ElasticsearchVersion.V7_1, @@ -709,8 +787,8 @@ describe('metrics', () => { test('Can use metricClusterIndexWriteBlocked on an Elasticsearch Domain', () => { testMetric( - (domain) => domain.metricClusterIndexWriteBlocked(), - 'ClusterIndexWriteBlocked', + (domain) => domain.metricClusterIndexWritesBlocked(), + 'ClusterIndexWritesBlocked', Statistic.MAXIMUM, Duration.minutes(1), ); @@ -1150,7 +1228,9 @@ describe('custom endpoints', () => { describe('custom error responses', () => { test('error when availabilityZoneCount does not match vpcOptions.subnets length', () => { - const vpc = new Vpc(stack, 'Vpc'); + const vpc = new Vpc(stack, 'Vpc', { + maxAzs: 1, + }); expect(() => new Domain(stack, 'Domain', { version: ElasticsearchVersion.V7_4, @@ -1158,16 +1238,7 @@ describe('custom error responses', () => { enabled: true, availabilityZoneCount: 2, }, - vpcOptions: { - subnets: [ - new Subnet(stack, 'Subnet', { - availabilityZone: 'testaz', - cidrBlock: vpc.vpcCidrBlock, - vpcId: vpc.vpcId, - }), - ], - securityGroups: [], - }, + vpc, })).toThrow(/you need to provide a subnet for each AZ you are using/); }); @@ -1357,31 +1428,7 @@ describe('custom error responses', () => { expect(() => new Domain(stack, 'Domain1', { version: ElasticsearchVersion.V7_4, - vpcOptions: { - subnets: [ - new Subnet(stack, 'Subnet1', { - availabilityZone: 'testaz1', - cidrBlock: vpc.vpcCidrBlock, - vpcId: vpc.vpcId, - }), - new Subnet(stack, 'Subnet2', { - availabilityZone: 'testaz2', - cidrBlock: vpc.vpcCidrBlock, - vpcId: vpc.vpcId, - }), - new Subnet(stack, 'Subnet3', { - availabilityZone: 'testaz3', - cidrBlock: vpc.vpcCidrBlock, - vpcId: vpc.vpcId, - }), - new Subnet(stack, 'Subnet4', { - availabilityZone: 'testaz4', - cidrBlock: vpc.vpcCidrBlock, - vpcId: vpc.vpcId, - }), - ], - securityGroups: [], - }, + vpc, zoneAwareness: { availabilityZoneCount: 4, }, diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json new file mode 100644 index 0000000000000..fc046ff8065b2 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.expected.json @@ -0,0 +1,591 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3EIP3A666A23": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3NATGateway7640CD1D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet3EIP3A666A23", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet3NATGateway7640CD1D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "cdk-integ-elasticsearch-vpc/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "DomainSecurityGroup48AA5FD6": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Security group for domain Domain", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "Domain66AC69E0": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "CognitoOptions": { + "Enabled": false + }, + "DomainEndpointOptions": { + "EnforceHTTPS": false, + "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" + }, + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10, + "VolumeType": "gp2" + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterEnabled": false, + "InstanceCount": 2, + "InstanceType": "r5.large.elasticsearch", + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 2 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "7.1", + "EncryptionAtRestOptions": { + "Enabled": false + }, + "LogPublishingOptions": {}, + "NodeToNodeEncryptionOptions": { + "Enabled": false + }, + "VPCOptions": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "DomainSecurityGroup48AA5FD6", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.ts b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.ts new file mode 100644 index 0000000000000..b1049270c6250 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticsearch/test/integ.elasticsearch-vpc.ts @@ -0,0 +1,28 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import { App, Stack, StackProps, RemovalPolicy } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as es from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'Vpc'); + const domainProps: es.DomainProps = { + version: es.ElasticsearchVersion.V7_1, + removalPolicy: RemovalPolicy.DESTROY, + vpc, + zoneAwareness: { + enabled: true, + }, + capacity: { + dataNodes: 2, + }, + }; + new es.Domain(this, 'Domain', domainProps); + } +} + +const app = new App(); +new TestStack(app, 'cdk-integ-elasticsearch-vpc'); +app.synth();