diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc1374f9..fa5d44040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.158.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.11.0...v1.158.0) (2022-07-26) + +* Upgraded all patterns to CDK v1.158.0 + ## [1.157.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.8.0...v1.157.0) (2022-06-13) * Upgraded all patterns to CDK v1.157.0 diff --git a/CHANGELOG.v2.md b/CHANGELOG.v2.md index 3809be8a1..87eae3d84 100644 --- a/CHANGELOG.v2.md +++ b/CHANGELOG.v2.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.11.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.10.0...v2.11.0) (2022-07-18) + +* Built upon underlying CDK version V2.24.0 + +### Features + +* **aws-lambda-elasticsearch-kibana:** added VPC support ([#718](https://github.com/awslabs/aws-solutions-constructs/issues/718)) ([33e8f17](https://github.com/awslabs/aws-solutions-constructs/commit/33e8f17a1d1df5be78882a8a59b54d689fea1e82)) + +## [2.10.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.9.0...v2.10.0) (2022-07-01) + +* Includes all functionality of V1.157.0 +* Built upon underlying CDK version V2.24.0 + +## [2.9.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.8.0...v2.9.0) (2022-06-13) + +* Includes all functionality of V1.157.0 +* Built upon underlying CDK version V2.23.0 + ## [2.8.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.7.0...v2.8.0) (2022-05-20) * Includes all functionality of V1.156.1 diff --git a/deployment/v2/align-version.js b/deployment/v2/align-version.js index 430b2420b..d798207ef 100755 --- a/deployment/v2/align-version.js +++ b/deployment/v2/align-version.js @@ -10,7 +10,7 @@ const findVersion = process.argv[2]; const replaceVersion = process.argv[3]; // these versions need to be sourced from a config file -const awsCdkLibVersion = '2.23.0'; +const awsCdkLibVersion = '2.24.0'; const constructsVersion = '10.0.0'; const MODULE_EXEMPTIONS = new Set([ '@aws-cdk/cloudformation-diff', diff --git a/source/lerna.json b/source/lerna.json index 700afd1b4..886f3571d 100644 --- a/source/lerna.json +++ b/source/lerna.json @@ -6,5 +6,5 @@ "./patterns/@aws-solutions-constructs/*" ], "rejectCycles": "true", - "version": "1.157.0" + "version": "1.158.0" } diff --git a/source/lerna.v2.json b/source/lerna.v2.json index ebc92f06a..0a3022dea 100644 --- a/source/lerna.v2.json +++ b/source/lerna.v2.json @@ -6,5 +6,5 @@ "./patterns/@aws-solutions-constructs/*" ], "rejectCycles": "true", - "version": "2.8.0" + "version": "2.11.0" } diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md index e8cedf598..8f1286c70 100755 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/README.md @@ -25,6 +25,13 @@ ## Overview This AWS Solutions Construct implements the AWS Lambda function and Amazon Elasticsearch Service with the least privileged permissions. +**Some cluster configurations (e.g VPC access) require the existence of the `AWSServiceRoleForAmazonElasticsearchService` Service-Linked Role in your account.** + +**You will need to create the service-linked role using the AWS CLI once in any account using this construct (it may have already been run to support other stacks):** +``` +aws iam create-service-linked-role --aws-service-name es.amazonaws.com +``` + Here is a minimal deployable pattern definition: Typescript @@ -105,7 +112,10 @@ new LambdaToElasticSearchAndKibana(this, "sample", |domainName|`string`|Domain name for the Cognito and the Elasticsearch Service| |cognitoDomainName?|`string`|Optional Cognito Domain Name, if provided it will be used for Cognito Domain, and domainName will be used for the Elasticsearch Domain| |createCloudWatchAlarms|`boolean`|Whether to create recommended CloudWatch alarms| -|domainEndpointEnvironmentVariableName?|`string`|Optional Name for the Lambda function environment variable set to the domain endpoint. Default: DOMAIN_ENDPOINT | +|domainEndpointEnvironmentVariableName?|`string`|Optional Name for the ElasticSearch domain endpoint environment variable set for the Lambda function.| +|existingVpc?|[`ec2.IVpc`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.IVpc.html)|An optional, existing VPC into which this pattern should be deployed. When deployed in a VPC, the Lambda function will use ENIs in the VPC to access network resources. If an existing VPC is provided, the `deployVpc` property cannot be `true`. This uses `ec2.IVpc` to allow clients to supply VPCs that exist outside the stack using the [`ec2.Vpc.fromLookup()`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.Vpc.html#static-fromwbrlookupscope-id-options) method.| +|vpcProps?|[`ec2.VpcProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.VpcProps.html)|Optional user provided properties to override the default properties for the new VPC. `enableDnsHostnames`, `enableDnsSupport`, `natGateways` and `subnetConfiguration` are set by the pattern, so any values for those properties supplied here will be overrriden. If `deployVpc` is not `true` then this property will be ignored.| +|deployVpc?|`boolean`|Whether to create a new VPC based on `vpcProps` into which to deploy this pattern. Setting this to true will deploy the minimal, most private VPC to run the pattern:If this property is `true` then `existingVpc` cannot be specified. Defaults to `false`.| ## Pattern Properties @@ -118,6 +128,7 @@ new LambdaToElasticSearchAndKibana(this, "sample", |elasticsearchDomain|[`elasticsearch.CfnDomain`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomain.html)|Returns an instance of elasticsearch.CfnDomain created by the construct| |elasticsearchDomain|[`iam.Role`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Role.html)|Returns an instance of iam.Role created by the construct for elasticsearch.CfnDomain| |cloudwatchAlarms?|[`cloudwatch.Alarm[]`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudwatch.Alarm.html)|Returns a list of cloudwatch.Alarm created by the construct| +|vpc?|[`ec2.IVpc`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.IVpc.html)|Returns an interface on the VPC used by the pattern (if any). This may be a VPC created by the pattern or the VPC supplied to the pattern constructor.| ## Lambda Function diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts index 60ee7c346..5380ed086 100644 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/lib/index.ts @@ -20,6 +20,7 @@ import * as defaults from '@aws-solutions-constructs/core'; import { Construct } from '@aws-cdk/core'; import { Role } from '@aws-cdk/aws-iam'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; /** * @summary The properties for the CognitoToApiGatewayToLambda Construct @@ -67,6 +68,24 @@ export interface LambdaToElasticSearchAndKibanaProps { * @default - DOMAIN_ENDPOINT */ readonly domainEndpointEnvironmentVariableName?: string; + /** + * An existing VPC for the construct to use (construct will NOT create a new VPC in this case) + * + * @default - None + */ + readonly existingVpc?: ec2.IVpc; + /** + * Properties to override default properties if deployVpc is true + * + * @default - DefaultIsolatedVpcProps() in vpc-defaults.ts + */ + readonly vpcProps?: ec2.VpcProps; + /** + * Whether to deploy a new VPC + * + * @default - false + */ + readonly deployVpc?: boolean; } export class LambdaToElasticSearchAndKibana extends Construct { @@ -77,6 +96,7 @@ export class LambdaToElasticSearchAndKibana extends Construct { public readonly elasticsearchRole: iam.Role; public readonly lambdaFunction: lambda.Function; public readonly cloudwatchAlarms?: cloudwatch.Alarm[]; + public readonly vpc?: ec2.IVpc; /** * @summary Constructs a new instance of the CognitoToApiGatewayToLambda class. @@ -90,37 +110,62 @@ export class LambdaToElasticSearchAndKibana extends Construct { super(scope, id); defaults.CheckProps(props); + if (props.vpcProps && !props.deployVpc) { + throw new Error("Error - deployVpc must be true when defining vpcProps"); + } + + if (props.lambdaFunctionProps?.vpc || props.lambdaFunctionProps?.vpcSubnets) { + throw new Error("Error - Define VPC using construct parameters not Lambda function props"); + } + + if (props.esDomainProps?.vpcOptions) { + throw new Error("Error - Define VPC using construct parameters not Elasticsearch props"); + } + + if (props.deployVpc || props.existingVpc) { + this.vpc = defaults.buildVpc(scope, { + defaultVpcProps: defaults.DefaultIsolatedVpcProps(), + existingVpc: props.existingVpc, + userVpcProps: props.vpcProps, + constructVpcProps: { + enableDnsHostnames: true, + enableDnsSupport: true, + }, + }); + } + this.lambdaFunction = defaults.buildLambdaFunction(this, { existingLambdaObj: props.existingLambdaObj, - lambdaFunctionProps: props.lambdaFunctionProps + lambdaFunctionProps: props.lambdaFunctionProps, + vpc: this.vpc }); // Find the lambda service Role ARN const lambdaFunctionRoleARN = this.lambdaFunction.role?.roleArn; - this.userPool = defaults.buildUserPool(this); - this.userPoolClient = defaults.buildUserPoolClient(this, this.userPool); - this.identityPool = defaults.buildIdentityPool(this, this.userPool, this.userPoolClient); - - let cognitoDomainName = props.domainName; - - if (props.cognitoDomainName) { - cognitoDomainName = props.cognitoDomainName; - } + let cognitoAuthorizedRole: iam.Role; - const cognitoAuthorizedRole: Role = defaults.setupCognitoForElasticSearch(this, cognitoDomainName, { - userpool: this.userPool, - identitypool: this.identityPool, - userpoolclient: this.userPoolClient - }); + [this.userPool, this.userPoolClient, this.identityPool, cognitoAuthorizedRole] = + this.setupCognito(this, props.cognitoDomainName ?? props.domainName); - [this.elasticsearchDomain, this.elasticsearchRole] = defaults.buildElasticSearch(this, props.domainName, { + const buildElasticSearchProps: any = { userpool: this.userPool, identitypool: this.identityPool, cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn, - serviceRoleARN: lambdaFunctionRoleARN}, props.esDomainProps); + serviceRoleARN: lambdaFunctionRoleARN, + vpc: this.vpc, + domainName: props.domainName, + clientDomainProps: props.esDomainProps + }; + + if (this.vpc) { + const securityGroupIds = defaults.getLambdaVpcSecurityGroupIds(this.lambdaFunction); + buildElasticSearchProps.securityGroupIds = securityGroupIds; + } + + [this.elasticsearchDomain, this.elasticsearchRole] = defaults.buildElasticSearch(this, buildElasticSearchProps); - // Add ES Domain to lambda envrionment variable + // Add ES Domain to lambda environment variable const domainEndpointEnvironmentVariableName = props.domainEndpointEnvironmentVariableName || 'DOMAIN_ENDPOINT'; this.lambdaFunction.addEnvironment(domainEndpointEnvironmentVariableName, this.elasticsearchDomain.attrDomainEndpoint); @@ -129,4 +174,18 @@ export class LambdaToElasticSearchAndKibana extends Construct { this.cloudwatchAlarms = defaults.buildElasticSearchCWAlarms(this); } } -} + + setupCognito(scope: Construct, domainName: string): [cognito.UserPool, cognito.UserPoolClient, cognito.CfnIdentityPool, iam.Role] { + const userPool = defaults.buildUserPool(scope); + const userPoolClient = defaults.buildUserPoolClient(scope, userPool); + const identityPool = defaults.buildIdentityPool(scope, userPool, userPoolClient); + + const cognitoAuthorizedRole: Role = defaults.setupCognitoForElasticSearch(scope, domainName, { + userpool: userPool, + identitypool: identityPool, + userpoolclient: userPoolClient + }); + + return [userPool, userPoolClient, identityPool, cognitoAuthorizedRole]; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json index e77215b4b..7c9104a72 100644 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/package.json @@ -56,6 +56,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/aws-cognito": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticsearch": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithClusterConfig.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithClusterConfig.expected.json new file mode 100644 index 000000000..be239d9b0 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithClusterConfig.expected.json @@ -0,0 +1,979 @@ +{ + "Resources": { + "testlambdaelasticsearchkibana5LambdaFunctionServiceRole26E43B12": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + } + }, + "testlambdaelasticsearchkibana5LambdaFunctionServiceRoleDefaultPolicy8A628F4B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibana5LambdaFunctionServiceRoleDefaultPolicy8A628F4B", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibana5LambdaFunctionServiceRole26E43B12" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC." + } + ] + } + } + }, + "testlambdaelasticsearchkibana5ReplaceDefaultSecurityGroupsecuritygroup375DBE67": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "deployFunctionWithClusterConfig/test-lambda-elasticsearch-kibana5/ReplaceDefaultSecurityGroup-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testlambdaelasticsearchkibana5LambdaFunction5382AC86": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db.zip" + }, + "Role": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5LambdaFunctionServiceRole26E43B12", + "Arn" + ] + }, + "Environment": { + "Variables": { + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5ElasticsearchDomain58F77409", + "DomainEndpoint" + ] + } + } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "TracingConfig": { + "Mode": "Active" + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5ReplaceDefaultSecurityGroupsecuritygroup375DBE67", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibana5LambdaFunctionServiceRoleDefaultPolicy8A628F4B", + "testlambdaelasticsearchkibana5LambdaFunctionServiceRole26E43B12" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions." + }, + { + "id": "W89", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + }, + { + "id": "W92", + "reason": "Impossible for us to define the correct concurrency for clients" + } + ] + } + } + }, + "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + }, + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testlambdaelasticsearchkibana5CognitoUserPoolClientB41FB91B": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] + } + }, + "testlambdaelasticsearchkibana5CognitoIdentityPool1B0A6046": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "testlambdaelasticsearchkibana5CognitoUserPoolClientB41FB91B" + }, + "ProviderName": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "testlambdaelasticsearchkibana5UserPoolDomainE2693371": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "deploytestwithclusterconfig", + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0" + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0" + ] + }, + "testlambdaelasticsearchkibana5CognitoAuthorizedRole784B2C89": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "testlambdaelasticsearchkibana5CognitoIdentityPool1B0A6046" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithclusterconfig/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "testlambdaelasticsearchkibana5IdentityPoolRoleMappingE355C8E9": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibana5CognitoIdentityPool1B0A6046" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5CognitoAuthorizedRole784B2C89", + "Arn" + ] + } + } + } + }, + "testlambdaelasticsearchkibana5CognitoKibanaConfigureRoleD2E8341A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testlambdaelasticsearchkibana5CognitoKibanaConfigureRolePolicy11560205": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "testlambdaelasticsearchkibana5CognitoIdentityPool1B0A6046" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithclusterconfig" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5CognitoKibanaConfigureRoleD2E8341A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibana5CognitoKibanaConfigureRolePolicy11560205", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibana5CognitoKibanaConfigureRoleD2E8341A" + } + ] + } + }, + "testlambdaelasticsearchkibana5ElasticsearchDomain58F77409": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5CognitoAuthorizedRole784B2C89", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5LambdaFunctionServiceRole26E43B12", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithclusterconfig/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibana5CognitoIdentityPool1B0A6046" + }, + "RoleArn": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5CognitoKibanaConfigureRoleD2E8341A", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana5CognitoUserPool4E321CD0" + } + }, + "DomainName": "deploytestwithclusterconfig", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 2, + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 2 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + }, + "VPCOptions": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana5ReplaceDefaultSecurityGroupsecuritygroup375DBE67", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + }, + { + "id": "W90", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + } + ] + } + } + }, + "testlambdaelasticsearchkibana5StatusRedAlarm916EC672": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana5StatusYellowAlarm7DCAF60A": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana5FreeStorageSpaceTooLowAlarmEC2C0D7B": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 20000 + } + }, + "testlambdaelasticsearchkibana5IndexWritesBlockedTooHighAlarmD496CE3E": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana5AutomatedSnapshotFailureTooHighAlarm97129BC4": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana5CPUUtilizationTooHighAlarm3BAAA397": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibana5JVMMemoryPressureTooHighAlarm251AD583": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibana5MasterCPUUtilizationTooHighAlarm97A330CC": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "testlambdaelasticsearchkibana5MasterJVMMemoryPressureTooHighAlarm7DABB351": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc" + } + ] + } + }, + "VpcisolatedSubnet1SubnetE62B1B9B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableE442650B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableAssociationD259E31A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet1RouteTableE442650B" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + } + }, + "VpcisolatedSubnet2Subnet39217055": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTable334F9764": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTableAssociation25A4716F": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet2RouteTable334F9764" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithClusterConfig/Vpc" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithClusterConfig.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithClusterConfig.ts new file mode 100644 index 000000000..5c6e98009 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithClusterConfig.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-constructs/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, defaults.generateIntegStackName(__filename), {}); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', +}; + +const esDomainProps = { + elasticsearchClusterConfig: { + dedicatedMasterEnabled: true, + dedicatedMasterCount: 3, + instanceCount: 2, + zoneAwarenessEnabled: true, + zoneAwarenessConfig: { + availabilityZoneCount: 2 + } + } +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana5', { + lambdaFunctionProps: lambdaProps, + domainName: "deploytestwithclusterconfig", + esDomainProps, + deployVpc: true, + vpcProps: { + maxAzs: 2 + } +}); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithExistingVpc.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithExistingVpc.expected.json new file mode 100644 index 000000000..18714b10a --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithExistingVpc.expected.json @@ -0,0 +1,1412 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "172.168.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "172.168.0.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PublicSubnet1" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/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": "deployFunctionWithExistingVpc/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "172.168.32.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PublicSubnet2" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/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": "deployFunctionWithExistingVpc/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "172.168.64.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PublicSubnet3" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/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": "deployFunctionWithExistingVpc/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3NATGateway7640CD1D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet3EIP3A666A23", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "172.168.96.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/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": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "172.168.128.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/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": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "172.168.160.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/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": "deployFunctionWithExistingVpc/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithExistingVpc/Vpc" + } + ] + } + }, + "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleA52BB7F9": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + } + }, + "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleDefaultPolicyA5AD88E5": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleDefaultPolicyA5AD88E5", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleA52BB7F9" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC." + } + ] + } + } + }, + "testlambdaelasticsearchkibana4ReplaceDefaultSecurityGroupsecuritygroupA79E2B92": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "deployFunctionWithExistingVpc/test-lambda-elasticsearch-kibana4/ReplaceDefaultSecurityGroup-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testlambdaelasticsearchkibana4LambdaFunction2C5856DF": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "S3Key": "67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db.zip" + }, + "Role": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleA52BB7F9", + "Arn" + ] + }, + "Environment": { + "Variables": { + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4ElasticsearchDomainC32C1BE6", + "DomainEndpoint" + ] + } + } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "TracingConfig": { + "Mode": "Active" + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4ReplaceDefaultSecurityGroupsecuritygroupA79E2B92", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleDefaultPolicyA5AD88E5", + "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleA52BB7F9" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions." + }, + { + "id": "W89", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + }, + { + "id": "W92", + "reason": "Impossible for us to define the correct concurrency for clients" + } + ] + } + } + }, + "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + }, + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testlambdaelasticsearchkibana4CognitoUserPoolClientABBF34C4": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] + } + }, + "testlambdaelasticsearchkibana4CognitoIdentityPool76EE9793": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "testlambdaelasticsearchkibana4CognitoUserPoolClientABBF34C4" + }, + "ProviderName": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "testlambdaelasticsearchkibana4UserPoolDomain4CAAF2F6": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "deploytestwithexistingvpc", + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1" + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1" + ] + }, + "testlambdaelasticsearchkibana4CognitoAuthorizedRoleA7D6B392": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "testlambdaelasticsearchkibana4CognitoIdentityPool76EE9793" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithexistingvpc/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "testlambdaelasticsearchkibana4IdentityPoolRoleMapping9378D177": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibana4CognitoIdentityPool76EE9793" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4CognitoAuthorizedRoleA7D6B392", + "Arn" + ] + } + } + } + }, + "testlambdaelasticsearchkibana4CognitoKibanaConfigureRole6A058B80": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testlambdaelasticsearchkibana4CognitoKibanaConfigureRolePolicy1615E937": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "testlambdaelasticsearchkibana4CognitoIdentityPool76EE9793" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithexistingvpc" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4CognitoKibanaConfigureRole6A058B80", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibana4CognitoKibanaConfigureRolePolicy1615E937", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibana4CognitoKibanaConfigureRole6A058B80" + } + ] + } + }, + "testlambdaelasticsearchkibana4ElasticsearchDomainC32C1BE6": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4CognitoAuthorizedRoleA7D6B392", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4LambdaFunctionServiceRoleA52BB7F9", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithexistingvpc/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibana4CognitoIdentityPool76EE9793" + }, + "RoleArn": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4CognitoKibanaConfigureRole6A058B80", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana4CognitoUserPool37A5CDE1" + } + }, + "DomainName": "deploytestwithexistingvpc", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 3 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + }, + "VPCOptions": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana4ReplaceDefaultSecurityGroupsecuritygroupA79E2B92", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + }, + { + "id": "W90", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + } + ] + } + } + }, + "testlambdaelasticsearchkibana4StatusRedAlarm56DEE5C7": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana4StatusYellowAlarm810B4F9E": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana4FreeStorageSpaceTooLowAlarmF3FB31EA": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 20000 + } + }, + "testlambdaelasticsearchkibana4IndexWritesBlockedTooHighAlarmF2968C92": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana4AutomatedSnapshotFailureTooHighAlarm53EB1ABB": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana4CPUUtilizationTooHighAlarm29B67D10": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibana4JVMMemoryPressureTooHighAlarm9DDED711": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibana4MasterCPUUtilizationTooHighAlarmE66867F2": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "testlambdaelasticsearchkibana4MasterJVMMemoryPressureTooHighAlarm83E1822E": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithExistingVpc.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithExistingVpc.ts new file mode 100644 index 000000000..c4e481177 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithExistingVpc.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-constructs/core'; + +const app = new App(); +const stack = new Stack(app, defaults.generateIntegStackName(__filename), { + env: { + region: process.env.CDK_DEFAULT_REGION, + account: process.env.CDK_DEFAULT_ACCOUNT, + } +}); + +// Create VPC +const vpc = defaults.getTestVpc(stack); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.fromAsset(`lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana4', { + lambdaFunctionProps: lambdaProps, + domainName: "deploytestwithexistingvpc", + existingVpc: vpc +}); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithVpcProps.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithVpcProps.expected.json new file mode 100644 index 000000000..aceea9831 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithVpcProps.expected.json @@ -0,0 +1,1033 @@ +{ + "Resources": { + "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleA3C1E07E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + } + }, + "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleDefaultPolicyA148ED7D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleDefaultPolicyA148ED7D", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleA3C1E07E" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC." + } + ] + } + } + }, + "testlambdaelasticsearchkibana3ReplaceDefaultSecurityGroupsecuritygroupEB497E1A": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "deployFunctionWithVpcProps/test-lambda-elasticsearch-kibana3/ReplaceDefaultSecurityGroup-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testlambdaelasticsearchkibana3LambdaFunctionE377CF86": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "S3Key": "67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db.zip" + }, + "Role": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleA3C1E07E", + "Arn" + ] + }, + "Environment": { + "Variables": { + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3ElasticsearchDomain268DE741", + "DomainEndpoint" + ] + } + } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "TracingConfig": { + "Mode": "Active" + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3ReplaceDefaultSecurityGroupsecuritygroupEB497E1A", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + }, + { + "Ref": "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleDefaultPolicyA148ED7D", + "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleA3C1E07E" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions." + }, + { + "id": "W89", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + }, + { + "id": "W92", + "reason": "Impossible for us to define the correct concurrency for clients" + } + ] + } + } + }, + "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + }, + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testlambdaelasticsearchkibana3CognitoUserPoolClient89D3C6A0": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] + } + }, + "testlambdaelasticsearchkibana3CognitoIdentityPool48956B3D": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "testlambdaelasticsearchkibana3CognitoUserPoolClient89D3C6A0" + }, + "ProviderName": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "testlambdaelasticsearchkibana3UserPoolDomain11719EB9": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "deploytestwithvpcprops", + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793" + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793" + ] + }, + "testlambdaelasticsearchkibana3CognitoAuthorizedRole2E055088": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "testlambdaelasticsearchkibana3CognitoIdentityPool48956B3D" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithvpcprops/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "testlambdaelasticsearchkibana3IdentityPoolRoleMappingDF65C594": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibana3CognitoIdentityPool48956B3D" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3CognitoAuthorizedRole2E055088", + "Arn" + ] + } + } + } + }, + "testlambdaelasticsearchkibana3CognitoKibanaConfigureRoleE8D8BE00": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testlambdaelasticsearchkibana3CognitoKibanaConfigureRolePolicyF1017A7A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "testlambdaelasticsearchkibana3CognitoIdentityPool48956B3D" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithvpcprops" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3CognitoKibanaConfigureRoleE8D8BE00", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibana3CognitoKibanaConfigureRolePolicyF1017A7A", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibana3CognitoKibanaConfigureRoleE8D8BE00" + } + ] + } + }, + "testlambdaelasticsearchkibana3ElasticsearchDomain268DE741": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3CognitoAuthorizedRole2E055088", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3LambdaFunctionServiceRoleA3C1E07E", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestwithvpcprops/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibana3CognitoIdentityPool48956B3D" + }, + "RoleArn": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3CognitoKibanaConfigureRoleE8D8BE00", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibana3CognitoUserPoolEF0D5793" + } + }, + "DomainName": "deploytestwithvpcprops", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 3 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + }, + "VPCOptions": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibana3ReplaceDefaultSecurityGroupsecuritygroupEB497E1A", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + }, + { + "Ref": "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + }, + { + "id": "W90", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + } + ] + } + } + }, + "testlambdaelasticsearchkibana3StatusRedAlarmFECC950E": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana3StatusYellowAlarmC7FC4662": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana3FreeStorageSpaceTooLowAlarmB9AD5427": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 20000 + } + }, + "testlambdaelasticsearchkibana3IndexWritesBlockedTooHighAlarm36E1D418": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana3AutomatedSnapshotFailureTooHighAlarmA83D4971": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibana3CPUUtilizationTooHighAlarmE77470C0": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibana3JVMMemoryPressureTooHighAlarm084D2991": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibana3MasterCPUUtilizationTooHighAlarm3331F781": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "testlambdaelasticsearchkibana3MasterJVMMemoryPressureTooHighAlarm149E8D14": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "172.168.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc" + } + ] + } + }, + "VpcisolatedSubnet1SubnetE62B1B9B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "172.168.0.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableE442650B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableAssociationD259E31A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet1RouteTableE442650B" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + } + }, + "VpcisolatedSubnet2Subnet39217055": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "172.168.64.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTable334F9764": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTableAssociation25A4716F": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet2RouteTable334F9764" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + } + }, + "VpcisolatedSubnet3Subnet44F2537D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "172.168.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc/isolatedSubnet3" + } + ] + } + }, + "VpcisolatedSubnet3RouteTableA2F6BBC0": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc/isolatedSubnet3" + } + ] + } + }, + "VpcisolatedSubnet3RouteTableAssociationDC010BEB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet3RouteTableA2F6BBC0" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet3Subnet44F2537D" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployFunctionWithVpcProps/Vpc" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithVpcProps.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithVpcProps.ts new file mode 100644 index 000000000..8242ff52f --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployFunctionWithVpcProps.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-constructs/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, defaults.generateIntegStackName(__filename), { + env: { + region: process.env.CDK_DEFAULT_REGION, + account: process.env.CDK_DEFAULT_ACCOUNT, + } +}); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana3', { + lambdaFunctionProps: lambdaProps, + domainName: "deploytestwithvpcprops", + deployVpc: true, + vpcProps: { + cidr: '172.168.0.0/16', + } +}); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployToFiveZones.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployToFiveZones.expected.json new file mode 100644 index 000000000..107822941 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployToFiveZones.expected.json @@ -0,0 +1,1035 @@ +{ + "Resources": { + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + } + }, + "testlambdaelasticsearchkibanaLambdaFunctionServiceRoleDefaultPolicy199413EB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibanaLambdaFunctionServiceRoleDefaultPolicy199413EB", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC." + } + ] + } + } + }, + "testlambdaelasticsearchkibanaReplaceDefaultSecurityGroupsecuritygroup9C517245": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "deployToFiveZones/test-lambda-elasticsearch-kibana/ReplaceDefaultSecurityGroup-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testlambdaelasticsearchkibanaLambdaFunction601D26D3": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db.zip" + }, + "Role": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2", + "Arn" + ] + }, + "Environment": { + "Variables": { + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaElasticsearchDomain50D5F86E", + "DomainEndpoint" + ] + } + } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "TracingConfig": { + "Mode": "Active" + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaReplaceDefaultSecurityGroupsecuritygroup9C517245", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + }, + { + "Ref": "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibanaLambdaFunctionServiceRoleDefaultPolicy199413EB", + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions." + }, + { + "id": "W89", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + }, + { + "id": "W92", + "reason": "Impossible for us to define the correct concurrency for clients" + } + ] + } + } + }, + "testlambdaelasticsearchkibanaCognitoUserPool9537802B": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + }, + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testlambdaelasticsearchkibanaCognitoUserPoolClient8F70A2AA": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] + } + }, + "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPoolClient8F70A2AA" + }, + "ProviderName": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoUserPool9537802B", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "testlambdaelasticsearchkibanaUserPoolDomainB9BDF063": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "deploytestfivezones", + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + ] + }, + "testlambdaelasticsearchkibanaCognitoAuthorizedRole88FAFCFA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestfivezones/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "testlambdaelasticsearchkibanaIdentityPoolRoleMappingBD0A239B": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoAuthorizedRole88FAFCFA", + "Arn" + ] + } + } + } + }, + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRolePolicyB7090E91": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoUserPool9537802B", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestfivezones" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibanaCognitoKibanaConfigureRolePolicyB7090E91", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1" + } + ] + } + }, + "testlambdaelasticsearchkibanaElasticsearchDomain50D5F86E": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoAuthorizedRole88FAFCFA", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/deploytestfivezones/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + }, + "RoleArn": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + } + }, + "DomainName": "deploytestfivezones", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 3 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + }, + "VPCOptions": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaReplaceDefaultSecurityGroupsecuritygroup9C517245", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + "Ref": "VpcisolatedSubnet2Subnet39217055" + }, + { + "Ref": "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + }, + { + "id": "W90", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + } + ] + } + } + }, + "testlambdaelasticsearchkibanaStatusRedAlarmCFCDB629": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaStatusYellowAlarm24B9D1CB": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaFreeStorageSpaceTooLowAlarm0B4D4E35": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 20000 + } + }, + "testlambdaelasticsearchkibanaIndexWritesBlockedTooHighAlarmB8C0E99C": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaAutomatedSnapshotFailureTooHighAlarm75F2676B": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaCPUUtilizationTooHighAlarmF16BA5D9": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibanaJVMMemoryPressureTooHighAlarm18224533": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibanaMasterCPUUtilizationTooHighAlarmE5E5999F": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "testlambdaelasticsearchkibanaMasterJVMMemoryPressureTooHighAlarm297FF1BE": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc" + } + ] + } + }, + "VpcisolatedSubnet1SubnetE62B1B9B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableE442650B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableAssociationD259E31A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet1RouteTableE442650B" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + } + }, + "VpcisolatedSubnet2Subnet39217055": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "10.0.64.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTable334F9764": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc/isolatedSubnet2" + } + ] + } + }, + "VpcisolatedSubnet2RouteTableAssociation25A4716F": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet2RouteTable334F9764" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet2Subnet39217055" + } + } + }, + "VpcisolatedSubnet3Subnet44F2537D": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "10.0.128.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc/isolatedSubnet3" + } + ] + } + }, + "VpcisolatedSubnet3RouteTableA2F6BBC0": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc/isolatedSubnet3" + } + ] + } + }, + "VpcisolatedSubnet3RouteTableAssociationDC010BEB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet3RouteTableA2F6BBC0" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet3Subnet44F2537D" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "deployToFiveZones/Vpc" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployToFiveZones.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployToFiveZones.ts new file mode 100644 index 000000000..8ee0e27f2 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.deployToFiveZones.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-constructs/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, defaults.generateIntegStackName(__filename), {}); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: lambdaProps, + domainName: "deploytestfivezones", + deployVpc: true, + vpcProps: { + maxAzs: 5 + } +}); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.disabledZoneAwareness.expected.json b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.disabledZoneAwareness.expected.json new file mode 100644 index 000000000..daf83505e --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.disabledZoneAwareness.expected.json @@ -0,0 +1,920 @@ +{ + "Resources": { + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRolePolicy" + } + ] + } + }, + "testlambdaelasticsearchkibanaLambdaFunctionServiceRoleDefaultPolicy199413EB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibanaLambdaFunctionServiceRoleDefaultPolicy199413EB", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Lambda needs the following minimum required permissions to send trace data to X-Ray and access ENIs in a VPC." + } + ] + } + } + }, + "testlambdaelasticsearchkibanaReplaceDefaultSecurityGroupsecuritygroup9C517245": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "disabledZoneAwareness/test-lambda-elasticsearch-kibana/ReplaceDefaultSecurityGroup-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testlambdaelasticsearchkibanaLambdaFunction601D26D3": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db.zip" + }, + "Role": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2", + "Arn" + ] + }, + "Environment": { + "Variables": { + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaElasticsearchDomain50D5F86E", + "DomainEndpoint" + ] + } + } + }, + "Handler": "index.handler", + "Runtime": "nodejs12.x", + "TracingConfig": { + "Mode": "Active" + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaReplaceDefaultSecurityGroupsecuritygroup9C517245", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + ] + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibanaLambdaFunctionServiceRoleDefaultPolicy199413EB", + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions." + }, + { + "id": "W89", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + }, + { + "id": "W92", + "reason": "Impossible for us to define the correct concurrency for clients" + } + ] + } + } + }, + "testlambdaelasticsearchkibanaCognitoUserPool9537802B": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + }, + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testlambdaelasticsearchkibanaCognitoUserPoolClient8F70A2AA": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + }, + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "profile", + "phone", + "email", + "openid", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO" + ] + } + }, + "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPoolClient8F70A2AA" + }, + "ProviderName": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoUserPool9537802B", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "testlambdaelasticsearchkibanaUserPoolDomainB9BDF063": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "disabledzoneawareness", + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + } + }, + "DependsOn": [ + "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + ] + }, + "testlambdaelasticsearchkibanaCognitoAuthorizedRole88FAFCFA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/disabledzoneawareness/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "testlambdaelasticsearchkibanaIdentityPoolRoleMappingBD0A239B": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoAuthorizedRole88FAFCFA", + "Arn" + ] + } + } + } + }, + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRolePolicyB7090E91": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoUserPool9537802B", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/disabledzoneawareness" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testlambdaelasticsearchkibanaCognitoKibanaConfigureRolePolicyB7090E91", + "Roles": [ + { + "Ref": "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1" + } + ] + } + }, + "testlambdaelasticsearchkibanaElasticsearchDomain50D5F86E": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoAuthorizedRole88FAFCFA", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaLambdaFunctionServiceRole3AFFEAA2", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/disabledzoneawareness/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoIdentityPoolC48068F0" + }, + "RoleArn": { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaCognitoKibanaConfigureRole8F40C1A1", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "testlambdaelasticsearchkibanaCognitoUserPool9537802B" + } + }, + "DomainName": "disabledzoneawareness", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessEnabled": false + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + }, + "VPCOptions": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "testlambdaelasticsearchkibanaReplaceDefaultSecurityGroupsecuritygroup9C517245", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + ] + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + }, + { + "id": "W90", + "reason": "This is not a rule for the general case, just for specific use cases/industries" + } + ] + } + } + }, + "testlambdaelasticsearchkibanaStatusRedAlarmCFCDB629": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaStatusYellowAlarm24B9D1CB": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaFreeStorageSpaceTooLowAlarm0B4D4E35": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 20000 + } + }, + "testlambdaelasticsearchkibanaIndexWritesBlockedTooHighAlarmB8C0E99C": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaAutomatedSnapshotFailureTooHighAlarm75F2676B": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "testlambdaelasticsearchkibanaCPUUtilizationTooHighAlarmF16BA5D9": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibanaJVMMemoryPressureTooHighAlarm18224533": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "testlambdaelasticsearchkibanaMasterCPUUtilizationTooHighAlarmE5E5999F": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "testlambdaelasticsearchkibanaMasterJVMMemoryPressureTooHighAlarm297FF1BE": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "disabledZoneAwareness/Vpc" + } + ] + } + }, + "VpcisolatedSubnet1SubnetE62B1B9B": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "10.0.0.0/18", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "isolated" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Isolated" + }, + { + "Key": "Name", + "Value": "disabledZoneAwareness/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableE442650B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "disabledZoneAwareness/Vpc/isolatedSubnet1" + } + ] + } + }, + "VpcisolatedSubnet1RouteTableAssociationD259E31A": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcisolatedSubnet1RouteTableE442650B" + }, + "SubnetId": { + "Ref": "VpcisolatedSubnet1SubnetE62B1B9B" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "disabledZoneAwareness/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "disabledZoneAwareness/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "disabledZoneAwareness/Vpc" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.disabledZoneAwareness.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.disabledZoneAwareness.ts new file mode 100644 index 000000000..c041f599f --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/integ.disabledZoneAwareness.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-constructs/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, defaults.generateIntegStackName(__filename), {}); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', +}; + +const esDomainProps = { + elasticsearchClusterConfig: { + dedicatedMasterCount: 3, + dedicatedMasterEnabled: true, + instanceCount: 3, + zoneAwarenessEnabled: false, + } +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: lambdaProps, + domainName: "disabledzoneawareness", + esDomainProps, + deployVpc: true, + vpcProps: { + maxAzs: 1 + } +}); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts index 20a374ba9..f19256510 100644 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts @@ -13,16 +13,14 @@ import { LambdaToElasticSearchAndKibana, LambdaToElasticSearchAndKibanaProps } from "../lib"; import * as lambda from '@aws-cdk/aws-lambda'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from "@aws-cdk/core"; import '@aws-cdk/assert/jest'; +import * as defaults from '@aws-solutions-constructs/core'; function deployNewFunc(stack: cdk.Stack) { const props: LambdaToElasticSearchAndKibanaProps = { - lambdaFunctionProps: { - code: lambda.Code.fromAsset(`${__dirname}/lambda`), - runtime: lambda.Runtime.NODEJS_14_X, - handler: 'index.handler' - }, + lambdaFunctionProps: getDefaultTestLambdaProps(), domainName: 'test-domain' }; @@ -78,11 +76,7 @@ test('check properties with no CW Alarms ', () => { const stack = new cdk.Stack(); const props: LambdaToElasticSearchAndKibanaProps = { - lambdaFunctionProps: { - code: lambda.Code.fromAsset(`${__dirname}/lambda`), - runtime: lambda.Runtime.NODEJS_14_X, - handler: 'index.handler' - }, + lambdaFunctionProps: getDefaultTestLambdaProps(), domainName: 'test-domain', createCloudWatchAlarms: false }; @@ -98,14 +92,10 @@ test('check properties with no CW Alarms ', () => { expect(construct.elasticsearchRole).toBeDefined(); }); -test('check lambda function ustom environment variable', () => { +test('check lambda function custom environment variable', () => { const stack = new cdk.Stack(); const props: LambdaToElasticSearchAndKibanaProps = { - lambdaFunctionProps: { - code: lambda.Code.fromAsset(`${__dirname}/lambda`), - runtime: lambda.Runtime.NODEJS_14_X, - handler: 'index.handler' - }, + lambdaFunctionProps: getDefaultTestLambdaProps(), domainName: 'test-domain', domainEndpointEnvironmentVariableName: 'CUSTOM_DOMAIN_ENDPOINT' }; @@ -132,11 +122,7 @@ test('check lambda function ustom environment variable', () => { test('check override cognito domain name with provided cognito domain name', () => { const stack = new cdk.Stack(); const props: LambdaToElasticSearchAndKibanaProps = { - lambdaFunctionProps: { - code: lambda.Code.fromAsset(`${__dirname}/lambda`), - runtime: lambda.Runtime.NODEJS_14_X, - handler: 'index.handler' - }, + lambdaFunctionProps: getDefaultTestLambdaProps(), domainName: 'test-domain', cognitoDomainName: 'test-cognito-domain' }; @@ -146,4 +132,410 @@ test('check override cognito domain name with provided cognito domain name', () expect(stack).toHaveResource('AWS::Cognito::UserPoolDomain', { Domain: 'test-cognito-domain' }); -}); \ No newline at end of file +}); + +test("Test minimal deployment that deploys a VPC in 2 AZ without vpcProps", () => { + const stack = new cdk.Stack(undefined, undefined, {}); + + new LambdaToElasticSearchAndKibana(stack, "lambda-elasticsearch-kibana-stack", { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: 'test-domain', + deployVpc: true, + }); + + expect(stack).toHaveResource("AWS::Lambda::Function", { + VpcConfig: { + SecurityGroupIds: [ + { + "Fn::GetAtt": [ + "lambdaelasticsearchkibanastackReplaceDefaultSecurityGroupsecuritygroup4C50002B", + "GroupId", + ], + }, + ], + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B", + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055", + } + ], + }, + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }); + + expect(stack).toHaveResource("AWS::EC2::VPC", { + EnableDnsHostnames: true, + EnableDnsSupport: true, + }); +}); + +test("Test minimal deployment that deploys a VPC in 3 AZ without vpcProps", () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + new LambdaToElasticSearchAndKibana(stack, "lambda-elasticsearch-kibana-stack", { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: 'test-domain', + deployVpc: true, + }); + + expect(stack).toHaveResource("AWS::Lambda::Function", { + VpcConfig: { + SecurityGroupIds: [ + { + "Fn::GetAtt": [ + "lambdaelasticsearchkibanastackReplaceDefaultSecurityGroupsecuritygroup4C50002B", + "GroupId", + ], + }, + ], + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B", + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055", + }, + { + Ref: "VpcisolatedSubnet3Subnet44F2537D", + }, + ], + }, + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055" + }, + { + Ref: "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }); + + expect(stack).toHaveResource("AWS::EC2::VPC", { + EnableDnsHostnames: true, + EnableDnsSupport: true, + }); +}); + +test("Test ES cluster deploy to 1 AZ when user set zoneAwarenessEnabled to false", () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const esDomainProps = { + elasticsearchClusterConfig: { + dedicatedMasterCount: 3, + dedicatedMasterEnabled: true, + zoneAwarenessEnabled: false, + instanceCount: 3 + } + }; + + new LambdaToElasticSearchAndKibana(stack, "lambda-elasticsearch-kibana-stack", { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: 'test-domain', + esDomainProps, + deployVpc: true, + vpcProps: { + maxAzs: 1 + } + }); + + expect(stack).toHaveResource("AWS::Elasticsearch::Domain", { + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessEnabled: false, + } + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B" + } + ] + } + }); +}); + +test("Test ES cluster deploy to 2 AZ when user set availabilityZoneCount to 2", () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const esDomainProps = { + elasticsearchClusterConfig: { + dedicatedMasterCount: 3, + dedicatedMasterEnabled: true, + instanceCount: 2, + zoneAwarenessEnabled: true, + zoneAwarenessConfig: { + availabilityZoneCount: 2 + } + } + }; + + new LambdaToElasticSearchAndKibana(stack, "lambda-elasticsearch-kibana-stack", { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: 'test-domain', + esDomainProps, + deployVpc: true, + vpcProps: { + maxAzs: 2 + } + }); + + expect(stack).toHaveResource("AWS::Elasticsearch::Domain", { + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 2, + ZoneAwarenessConfig: { + AvailabilityZoneCount: 2, + }, + ZoneAwarenessEnabled: true, + } + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055" + } + ] + } + }); +}); + +test('Test minimal deployment with an existing isolated VPC', () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const vpc = defaults.getTestVpc(stack, false, { + vpcName: "existing-isolated-vpc-test" + }); + + const construct = new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: "test", + existingVpc: vpc + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + Tags: [ + { + Key: "Name", + Value: "existing-isolated-vpc-test" + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055" + }, + { + Ref: "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }); + + expect(stack).toCountResources("AWS::EC2::VPC", 1); + expect(construct.vpc).toBeDefined(); +}); + +test('Test minimal deployment with an existing private VPC', () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const vpc = new ec2.Vpc(stack, 'existing-private-vpc-test', { + natGateways: 1, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'application', + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + }, + { + cidrMask: 24, + name: "public", + subnetType: ec2.SubnetType.PUBLIC, + } + ] + }); + + const construct = new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: "test", + existingVpc: vpc + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + Tags: [ + { + Key: "Name", + Value: "Default/existing-private-vpc-test" + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "existingprivatevpctestapplicationSubnet1Subnet1F7744F0" + }, + { + Ref: "existingprivatevpctestapplicationSubnet2SubnetF7B713AD" + }, + { + Ref: "existingprivatevpctestapplicationSubnet3SubnetA519E038" + } + ] + } + }); + + expect(stack).toCountResources("AWS::EC2::VPC", 1); + expect(construct.vpc).toBeDefined(); +}); + +test('Test minimal deployment with VPC construct props', () => { + const stack = new cdk.Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const construct = new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: "test", + deployVpc: true, + vpcProps: { + vpcName: "vpc-props-test" + } + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + Tags: [ + { + Key: "Name", + Value: "vpc-props-test" + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::Elasticsearch::Domain", { + VPCOptions: { + SubnetIds: [ + { + Ref: "VpcisolatedSubnet1SubnetE62B1B9B" + }, + { + Ref: "VpcisolatedSubnet2Subnet39217055" + }, + { + Ref: "VpcisolatedSubnet3Subnet44F2537D" + } + ] + } + }); + + expect(stack).toCountResources("AWS::EC2::VPC", 1); + expect(construct.vpc).toBeDefined(); +}); + +test('Test error for vpcProps and undefined deployVpc prop', () => { + const stack = new cdk.Stack(); + + const app = () => { + new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: getDefaultTestLambdaProps(), + domainName: "test", + vpcProps: { + vpcName: "existing-vpc-test" + } + }); + }; + + expect(app).toThrowError("Error - deployVpc must be true when defining vpcProps"); +}); + +test('Test error for Lambda function VPC props', () => { + const stack = new cdk.Stack(); + + const vpc = defaults.getTestVpc(stack); + + const app = () => { + new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: defaults.consolidateProps(getDefaultTestLambdaProps(), { vpc }), + domainName: "test", + deployVpc: true, + }); + }; + + expect(app).toThrowError("Error - Define VPC using construct parameters not Lambda function props"); +}); + +test('Test error for Elasticsearch domain VPC props', () => { + const stack = new cdk.Stack(); + + const app = () => { + new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: getDefaultTestLambdaProps(), + esDomainProps: { + vpcOptions: { + subnetIds: ['fake-ids'], + securityGroupIds: ['fake-sgs'] + } + }, + domainName: "test", + deployVpc: true, + }); + }; + + expect(app).toThrowError("Error - Define VPC using construct parameters not Elasticsearch props"); +}); + +function getDefaultTestLambdaProps(): lambda.FunctionProps { + return { + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + }; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-route53-alb/README.md b/source/patterns/@aws-solutions-constructs/aws-route53-alb/README.md index 99f4ae37d..b1c53c50c 100755 --- a/source/patterns/@aws-solutions-constructs/aws-route53-alb/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-route53-alb/README.md @@ -128,7 +128,7 @@ Out of the box implementation of the Construct without any override will set the * Adds an ALIAS record to the new or provided Hosted Zone that routes to the construct's ALB ### Application Load Balancer -* Creates an Application Load Balancer with no Listener or target. The consruct can incorporate an existing, fully configured ALB if provided. +* Creates an Application Load Balancer with no Listener or target. The construct can incorporate an existing, fully configured ALB if provided. ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-defaults.ts index 7764f471f..dfbe3167f 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-defaults.ts @@ -12,24 +12,18 @@ */ import * as elasticsearch from '@aws-cdk/aws-elasticsearch'; -import * as cognito from '@aws-cdk/aws-cognito'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; +import { BuildElasticSearchProps } from './elasticsearch-helper'; -export interface CfnDomainOptions { - readonly identitypool: cognito.CfnIdentityPool, - readonly userpool: cognito.UserPool, - readonly cognitoAuthorizedRoleARN: string, - readonly serviceRoleARN?: string -} - -export function DefaultCfnDomainProps(domainName: string, cognitoKibanaConfigureRole: iam.Role, options: CfnDomainOptions) { +export function DefaultCfnDomainProps(domainName: string, cognitoKibanaConfigureRole: iam.Role, props: BuildElasticSearchProps): + elasticsearch.CfnDomainProps { const roleARNs: iam.IPrincipal[] = []; - roleARNs.push(new iam.ArnPrincipal(options.cognitoAuthorizedRoleARN)); + roleARNs.push(new iam.ArnPrincipal(props.cognitoAuthorizedRoleARN)); - if (options.serviceRoleARN) { - roleARNs.push(new iam.ArnPrincipal(options.serviceRoleARN)); + if (props.serviceRoleARN) { + roleARNs.push(new iam.ArnPrincipal(props.serviceRoleARN)); } return { @@ -41,15 +35,6 @@ export function DefaultCfnDomainProps(domainName: string, cognitoKibanaConfigure nodeToNodeEncryptionOptions: { enabled: true }, - elasticsearchClusterConfig: { - dedicatedMasterEnabled: true, - dedicatedMasterCount: 3, - instanceCount: 3, - zoneAwarenessEnabled: true, - zoneAwarenessConfig: { - availabilityZoneCount: 3 - } - }, snapshotOptions: { automatedSnapshotStartHour: 1 }, @@ -59,8 +44,8 @@ export function DefaultCfnDomainProps(domainName: string, cognitoKibanaConfigure }, cognitoOptions: { enabled: true, - identityPoolId: options.identitypool.ref, - userPoolId: options.userpool.userPoolId, + identityPoolId: props.identitypool.ref, + userPoolId: props.userpool.userPoolId, roleArn: cognitoKibanaConfigureRole.roleArn }, accessPolicies: new iam.PolicyDocument({ diff --git a/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-helper.ts index 0ab62d3d9..503e9976b 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/elasticsearch-helper.ts @@ -12,65 +12,65 @@ */ import * as elasticsearch from '@aws-cdk/aws-elasticsearch'; -import { CfnDomainOptions, DefaultCfnDomainProps } from './elasticsearch-defaults'; +import { DefaultCfnDomainProps } from './elasticsearch-defaults'; import { consolidateProps, addCfnSuppressRules } from './utils'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import * as cognito from '@aws-cdk/aws-cognito'; +import * as ec2 from '@aws-cdk/aws-ec2'; // Note: To ensure CDKv2 compatibility, keep the import statement for Construct separate import { Construct } from '@aws-cdk/core'; -export function buildElasticSearch(scope: Construct, domainName: string, - options: CfnDomainOptions, cfnDomainProps?: elasticsearch.CfnDomainProps): [elasticsearch.CfnDomain, iam.Role] { +const MaximumAzsInElasticsearchDomain = 3; + +export interface BuildElasticSearchProps { + readonly identitypool: cognito.CfnIdentityPool; + readonly userpool: cognito.UserPool; + readonly cognitoAuthorizedRoleARN: string; + readonly serviceRoleARN?: string; + readonly vpc?: ec2.IVpc; + readonly domainName: string; + readonly clientDomainProps?: elasticsearch.CfnDomainProps, + readonly securityGroupIds?: string[] +} + +export function buildElasticSearch(scope: Construct, props: BuildElasticSearchProps): [elasticsearch.CfnDomain, iam.Role] { + + let subnetIds: string[] = []; + const constructDrivenProps: any = {}; // Setup the IAM Role & policy for ES to configure Cognito User pool and Identity pool - const cognitoKibanaConfigureRole = new iam.Role(scope, 'CognitoKibanaConfigureRole', { - assumedBy: new iam.ServicePrincipal('es.amazonaws.com') - }); - - const cognitoKibanaConfigureRolePolicy = new iam.Policy(scope, 'CognitoKibanaConfigureRolePolicy', { - statements: [ - new iam.PolicyStatement({ - actions: [ - "cognito-idp:DescribeUserPool", - "cognito-idp:CreateUserPoolClient", - "cognito-idp:DeleteUserPoolClient", - "cognito-idp:DescribeUserPoolClient", - "cognito-idp:AdminInitiateAuth", - "cognito-idp:AdminUserGlobalSignOut", - "cognito-idp:ListUserPoolClients", - "cognito-identity:DescribeIdentityPool", - "cognito-identity:UpdateIdentityPool", - "cognito-identity:SetIdentityPoolRoles", - "cognito-identity:GetIdentityPoolRoles", - "es:UpdateElasticsearchDomainConfig" - ], - resources: [ - options.userpool.userPoolArn, - `arn:aws:cognito-identity:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:identitypool/${options.identitypool.ref}`, - `arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${domainName}` - ] - }), - new iam.PolicyStatement({ - actions: [ - "iam:PassRole" - ], - conditions: { - StringLike: { 'iam:PassedToService': 'cognito-identity.amazonaws.com' } - }, - resources: [ - cognitoKibanaConfigureRole.roleArn - ] - }) - ] - }); - cognitoKibanaConfigureRolePolicy.attachToRole(cognitoKibanaConfigureRole); + const cognitoKibanaConfigureRole = createKibanaCognitoRole(scope, props.userpool, props.identitypool, props.domainName); + + if (props.vpc) { + subnetIds = retrievePrivateSubnetIds(props.vpc); + + if (subnetIds.length > MaximumAzsInElasticsearchDomain) { + subnetIds = subnetIds.slice(0, MaximumAzsInElasticsearchDomain); + } - let _cfnDomainProps = DefaultCfnDomainProps(domainName, cognitoKibanaConfigureRole, options); + constructDrivenProps.vpcOptions = { + subnetIds, + securityGroupIds: props.securityGroupIds + }; - _cfnDomainProps = consolidateProps(_cfnDomainProps, cfnDomainProps); + // If the client did not submit a ClusterConfig, then we will create one + if (!props.clientDomainProps?.elasticsearchClusterConfig) { + constructDrivenProps.elasticsearchClusterConfig = createClusterConfiguration(subnetIds.length); + } + } else { // No VPC + // If the client did not submit a ClusterConfig, then we will create one based on the Region + if (!props.clientDomainProps?.elasticsearchClusterConfig) { + constructDrivenProps.elasticsearchClusterConfig = createClusterConfiguration(cdk.Stack.of(scope).availabilityZones.length); + } + } + + const defaultCfnDomainProps = DefaultCfnDomainProps(props.domainName, cognitoKibanaConfigureRole, props); + const finalCfnDomainProps = consolidateProps(defaultCfnDomainProps, props.clientDomainProps, constructDrivenProps); + + const esDomain = new elasticsearch.CfnDomain(scope, `ElasticsearchDomain`, finalCfnDomainProps); - const esDomain = new elasticsearch.CfnDomain(scope, "ElasticsearchDomain", _cfnDomainProps); addCfnSuppressRules(esDomain, [ { id: "W28", @@ -217,3 +217,92 @@ export function buildElasticSearchCWAlarms(scope: Construct): cloudwatch.Alarm[] return alarms; } + +function retrievePrivateSubnetIds(vpc: ec2.IVpc) { + let targetSubnetType; + + if (vpc.isolatedSubnets.length) { + targetSubnetType = ec2.SubnetType.PRIVATE_ISOLATED; + } else if (vpc.privateSubnets.length) { + targetSubnetType = ec2.SubnetType.PRIVATE_WITH_NAT; + } else { + throw new Error('Error - ElasticSearch Domains can only be deployed in Isolated or Private subnets'); + } + + const subnetSelector = { + onePerAz: true, + subnetType: targetSubnetType + }; + + return vpc.selectSubnets(subnetSelector).subnetIds; +} + +function createClusterConfiguration(numberOfAzs?: number): elasticsearch.CfnDomain.ElasticsearchClusterConfigProperty { + return { + dedicatedMasterEnabled: true, + dedicatedMasterCount: 3, + zoneAwarenessEnabled: true, + zoneAwarenessConfig: { + availabilityZoneCount: numberOfAzs + }, + instanceCount: numberOfAzs, + }; +} + +function createKibanaCognitoRole( + scope: Construct, + userPool: cognito.UserPool, + identitypool: cognito.CfnIdentityPool, + domainName: string +): iam.Role { + // Setup the IAM Role & policy for ES to configure Cognito User pool and Identity pool + const cognitoKibanaConfigureRole = new iam.Role( + scope, + "CognitoKibanaConfigureRole", + { + assumedBy: new iam.ServicePrincipal("es.amazonaws.com"), + } + ); + + const cognitoKibanaConfigureRolePolicy = new iam.Policy( + scope, + "CognitoKibanaConfigureRolePolicy", + { + statements: [ + new iam.PolicyStatement({ + actions: [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig", + ], + resources: [ + userPool.userPoolArn, + `arn:aws:cognito-identity:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:identitypool/${identitypool.ref}`, + `arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${domainName}`, + ], + }), + new iam.PolicyStatement({ + actions: ["iam:PassRole"], + conditions: { + StringLike: { + "iam:PassedToService": "cognito-identity.amazonaws.com", + }, + }, + resources: [cognitoKibanaConfigureRole.roleArn], + }), + ], + } + ); + + cognitoKibanaConfigureRolePolicy.attachToRole(cognitoKibanaConfigureRole); + return cognitoKibanaConfigureRole; +} diff --git a/source/patterns/@aws-solutions-constructs/core/lib/lambda-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/lambda-helper.ts index 272cb2545..8da6aabfd 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/lambda-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/lambda-helper.ts @@ -134,7 +134,7 @@ export function deployLambdaFunction(scope: Construct, ); finalLambdaFunctionProps = overrideProps(finalLambdaFunctionProps, { - securityGroups: [ lambdaSecurityGroup ], + securityGroups: [lambdaSecurityGroup], vpc, }, true); } @@ -210,4 +210,12 @@ function GetNextId(children: IConstruct[], coreName: string): string { }); return `${coreName}-${lastSuffix + 1}`; +} + +export function getLambdaVpcSecurityGroupIds(lambdaFunction: lambda.Function): string[] { + const securityGroupIds: string[] = []; + + lambdaFunction.connections.securityGroups.forEach(element => securityGroupIds.push(element.securityGroupId)); + + return securityGroupIds; } \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/lib/vpc-defaults.ts b/source/patterns/@aws-solutions-constructs/core/lib/vpc-defaults.ts index 63a0d0734..acc2e7794 100644 --- a/source/patterns/@aws-solutions-constructs/core/lib/vpc-defaults.ts +++ b/source/patterns/@aws-solutions-constructs/core/lib/vpc-defaults.ts @@ -48,6 +48,11 @@ export function DefaultPrivateVpcProps(): ec2.VpcProps { cidrMask: 18, name: "private", subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + }, + { + cidrMask: 24, + name: "public", + subnetType: ec2.SubnetType.PUBLIC, } ] } as ec2.VpcProps; diff --git a/source/patterns/@aws-solutions-constructs/core/package.json b/source/patterns/@aws-solutions-constructs/core/package.json index c5117c973..aae093975 100644 --- a/source/patterns/@aws-solutions-constructs/core/package.json +++ b/source/patterns/@aws-solutions-constructs/core/package.json @@ -154,4 +154,4 @@ "@aws-cdk/aws-ssm": "0.0.0", "@aws-cdk/aws-wafv2": "0.0.0" } -} +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/test/elasticsearch-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/elasticsearch-helper.test.ts index 08b21d694..a5029db64 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/elasticsearch-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/elasticsearch-helper.test.ts @@ -16,9 +16,10 @@ import * as elasticsearch from '@aws-cdk/aws-elasticsearch'; import * as defaults from '../index'; import '@aws-cdk/assert/jest'; import * as iam from '@aws-cdk/aws-iam'; +import * as ec2 from '@aws-cdk/aws-ec2'; -function deployES(stack: Stack, domainName: string, cfnDomainProps?: elasticsearch.CfnDomainProps, - lambdaRoleARN?: string): [elasticsearch.CfnDomain, iam.Role] { +function deployES(stack: Stack, domainName: string, clientDomainProps?: elasticsearch.CfnDomainProps, + lambdaRoleARN?: string, vpc?: ec2.IVpc): [elasticsearch.CfnDomain, iam.Role] { const userpool = defaults.buildUserPool(stack); const userpoolclient = defaults.buildUserPoolClient(stack, userpool, { userPoolClientName: 'test', @@ -33,23 +34,32 @@ function deployES(stack: Stack, domainName: string, cfnDomainProps?: elasticsear }); if (lambdaRoleARN) { - return defaults.buildElasticSearch(stack, domainName, { + return defaults.buildElasticSearch(stack, { userpool, identitypool, cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn, - serviceRoleARN: lambdaRoleARN - }, cfnDomainProps); + serviceRoleARN: lambdaRoleARN, + vpc, + domainName, + clientDomainProps + }); } else { - return defaults.buildElasticSearch(stack, domainName, { + return defaults.buildElasticSearch(stack, { userpool, identitypool, - cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn - }, cfnDomainProps); + cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn, + vpc, + domainName, + clientDomainProps + }); } } test('Test override SnapshotOptions for buildElasticSearch', () => { - const stack = new Stack(); + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + deployES(stack, 'test-domain', { snapshotOptions: { automatedSnapshotStartHour: 5 @@ -132,8 +142,138 @@ test('Test override SnapshotOptions for buildElasticSearch', () => { }); }); +test('Test VPC with 1 AZ, Zone Awareness Disabled', () => { + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const vpc = defaults.getTestVpc(stack, false); + + deployES(stack, 'test-domain', { + elasticsearchClusterConfig: { + dedicatedMasterEnabled: true, + dedicatedMasterCount: 3, + instanceCount: 3, + zoneAwarenessEnabled: false + } + }, undefined, vpc); + + expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', { + DomainName: "test-domain", + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessEnabled: false + } + }); +}); + +test('Test VPC with 2 AZ, Zone Awareness Enabled', () => { + // If no environment is specified, a VPC will use 2 AZs by default. + // If an environment is specified, a VPC will use 3 AZs by default. + const stack = new Stack(undefined, undefined, {}); + + const vpc: ec2.IVpc = defaults.getTestVpc(stack, false); + + deployES(stack, 'test-domain', {}, undefined, vpc); + + expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', { + DomainName: "test-domain", + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 2, + ZoneAwarenessEnabled: true + } + }); +}); + +test('Test VPC with 3 AZ, Zone Awareness Enabled', () => { + // If no environment is specified, a VPC will use 2 AZs by default. + // If an environment is specified, a VPC will use 3 AZs by default. + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const vpc: ec2.IVpc = defaults.getTestVpc(stack); + + deployES(stack, 'test-domain', {}, undefined, vpc); + + expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', { + DomainName: "test-domain", + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessEnabled: true + } + }); +}); + +test('Test deployment with an existing private VPC', () => { + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const vpc = new ec2.Vpc(stack, 'existing-private-vpc-test', { + natGateways: 1, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'application', + subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, + }, + { + cidrMask: 24, + name: "public", + subnetType: ec2.SubnetType.PUBLIC, + } + ] + }); + + deployES(stack, 'test-domain', {}, undefined, vpc); + + expect(stack).toHaveResourceLike('AWS::Elasticsearch::Domain', { + DomainName: "test-domain", + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessEnabled: true + } + }); +}); + +test('Test error thrown with no private subnet configurations', () => { + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + + const vpc = defaults.buildVpc(stack, { + defaultVpcProps: { + subnetConfiguration: [ + { + cidrMask: 18, + name: "public", + subnetType: ec2.SubnetType.PUBLIC, + } + ] + } + }); + + const app = () => { + deployES(stack, 'test-domain', {}, undefined, vpc); + }; + + expect(app).toThrowError('Error - ElasticSearch Domains can only be deployed in Isolated or Private subnets'); +}); + test('Test override ES version for buildElasticSearch', () => { - const stack = new Stack(); + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + deployES(stack, 'test-domain', { elasticsearchVersion: '7.0' }); @@ -216,7 +356,10 @@ test('Test override ES version for buildElasticSearch', () => { }); test('Test ES with lambdaRoleARN', () => { - const stack = new Stack(); + const stack = new Stack(undefined, undefined, { + env: { account: "123456789012", region: 'us-east-1' }, + }); + deployES(stack, 'test-domain', {}, 'arn:aws:us-east-1:mylambdaRoleARN'); expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { diff --git a/source/patterns/@aws-solutions-constructs/core/test/lambda-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/lambda-helper.test.ts index a47bf2ad5..efd4db97b 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/lambda-helper.test.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/lambda-helper.test.ts @@ -255,7 +255,7 @@ test("Test for error if VPC in arguments AND in Lambda Function properties", () vpc: fakeVpc, }; - const app = () => { + const app = () => { defaults.deployLambdaFunction(stack, lambdaFunctionProps, undefined, fakeVpc); }; @@ -426,4 +426,25 @@ test('Test minimum deployment with an existing VPC as a vpc parameter in deployL ] } }); +}); + +test("Test retrieving lambda vpc security group ids", () => { + const stack = new Stack(); + + const vpc = defaults.getTestVpc(stack); + const securityGroup1 = new ec2.SecurityGroup(stack, 'SecurityGroup1', { vpc }); + const securityGroup2 = new ec2.SecurityGroup(stack, 'SecurityGroup2', { vpc }); + + const testLambdaFunction = new lambda.Function(stack, 'test-lamba', { + runtime: lambda.Runtime.NODEJS_14_X, + handler: "index.handler", + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + securityGroups: [securityGroup1, securityGroup2], + vpc + }); + + const securityGroups = defaults.getLambdaVpcSecurityGroupIds(testLambdaFunction); + + expect(securityGroups).toContain(securityGroup1.securityGroupId); + expect(securityGroups).toContain(securityGroup2.securityGroupId); }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/core/test/test-helper.ts b/source/patterns/@aws-solutions-constructs/core/test/test-helper.ts index 189e58dd4..4ae7871b2 100644 --- a/source/patterns/@aws-solutions-constructs/core/test/test-helper.ts +++ b/source/patterns/@aws-solutions-constructs/core/test/test-helper.ts @@ -79,7 +79,7 @@ export function generateIntegStackName(filename: string): string { } // Helper Functions -export function getTestVpc(stack: Stack, publicFacing: boolean = true) { +export function getTestVpc(stack: Stack, publicFacing: boolean = true, userVpcProps?: ec2.VpcProps) { return buildVpc(stack, { defaultVpcProps: publicFacing ? DefaultPublicPrivateVpcProps() : @@ -89,6 +89,7 @@ export function getTestVpc(stack: Stack, publicFacing: boolean = true) { enableDnsSupport: true, cidr: '172.168.0.0/16', }, + userVpcProps }); }