diff --git a/packages/@aws-cdk/aws-synthetics/README.md b/packages/@aws-cdk/aws-synthetics/README.md index cdb5aaa6dff47..6ee19906e5800 100644 --- a/packages/@aws-cdk/aws-synthetics/README.md +++ b/packages/@aws-cdk/aws-synthetics/README.md @@ -173,6 +173,32 @@ new synthetics.Canary(this, 'Bucket Canary', { > > See Synthetics [docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary.html). +### Running a canary on a VPC + +You can specify what [VPC a canary executes in](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_VPC.html). +This can allow for monitoring services that may be internal to a specific VPC. To place a canary within a VPC, you can specify the `vpc` property with the desired `VPC` to place then canary in. +This will automatically attach the appropriate IAM permissions to attach to the VPC. This will also create a Security Group and attach to the default subnets for the VPC unless specified via `vpcSubnets` and `securityGroups`. + +```ts +import * as ec2 from '@aws-cdk/aws-ec2'; + +declare const vpc: ec2.IVpc; +new synthetics.Canary(this, 'Vpc Canary', { + test: synthetics.Test.custom({ + code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), + handler: 'index.handler', + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, + vpc, +}); +``` + +> **Note:** By default, the Synthetics runtime needs access to the S3 and CloudWatch APIs, which will fail in a private subnet without internet access enabled (e.g. an isolated subnnet). +> +> Ensure that the Canary is placed in a VPC either with internet connectivity or with VPC Endpoints for S3 and CloudWatch enabled and configured. +> +> See [Synthetics VPC docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_VPC.html). + ### Alarms You can configure a CloudWatch Alarm on a canary metric. Metrics are emitted by CloudWatch automatically and can be accessed by the following APIs: diff --git a/packages/@aws-cdk/aws-synthetics/lib/canary.ts b/packages/@aws-cdk/aws-synthetics/lib/canary.ts index fab47a960289a..717bfcbe6082d 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/canary.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/canary.ts @@ -1,5 +1,6 @@ import * as crypto from 'crypto'; import { Metric, MetricOptions, MetricProps } from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; @@ -179,12 +180,36 @@ export interface CanaryProps { * @default - No environment variables. */ readonly environmentVariables?: { [key: string]: string }; + + /** + * The VPC where this canary is run. + * + * Specify this if the canary needs to access resources in a VPC. + * + * @default - Not in VPC + */ + readonly vpc?: ec2.IVpc; + + /** + * Where to place the network interfaces within the VPC. You must provide `vpc` when using this prop. + * + * @default - the Vpc default strategy if not specified + */ + readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * The list of security groups to associate with the canary's network interfaces. You must provide `vpc` when using this prop. + * + * @default - If the canary is placed within a VPC and a security group is + * not specified a dedicated security group will be created for this canary. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; } /** * Define a new Canary */ -export class Canary extends cdk.Resource { +export class Canary extends cdk.Resource implements ec2.IConnectable { /** * Execution role associated with this Canary. */ @@ -213,6 +238,14 @@ export class Canary extends cdk.Resource { */ public readonly artifactsBucket: s3.IBucket; + /** + * Actual connections object for the underlying Lambda + * + * May be unset, in which case the canary Lambda is not configured for use in a VPC. + * @internal + */ + private readonly _connections?: ec2.Connections; + public constructor(scope: Construct, id: string, props: CanaryProps) { if (props.canaryName && !cdk.Token.isUnresolved(props.canaryName)) { validateName(props.canaryName); @@ -229,7 +262,12 @@ export class Canary extends cdk.Resource { enforceSSL: true, }); - this.role = props.role ?? this.createDefaultRole(props.artifactsBucketLocation?.prefix); + this.role = props.role ?? this.createDefaultRole(props); + + if (props.vpc) { + // Security Groups are created and/or appended in `createVpcConfig`. + this._connections = new ec2.Connections({}); + } const resource: CfnCanary = new CfnCanary(this, 'Resource', { artifactS3Location: this.artifactsBucket.s3UrlForObject(props.artifactsBucketLocation?.prefix), @@ -242,6 +280,7 @@ export class Canary extends cdk.Resource { successRetentionPeriod: props.successRetentionPeriod?.toDays(), code: this.createCode(props), runConfig: this.createRunConfig(props), + vpcConfig: this.createVpcConfig(props), }); this.canaryId = resource.attrId; @@ -249,6 +288,19 @@ export class Canary extends cdk.Resource { this.canaryName = this.getResourceNameAttribute(resource.ref); } + /** + * Access the Connections object + * + * Will fail if not a VPC-enabled Canary + */ + public get connections(): ec2.Connections { + if (!this._connections) { + // eslint-disable-next-line max-len + throw new Error('Only VPC-associated Canaries have security groups to manage. Supply the "vpc" parameter when creating the Canary.'); + } + return this._connections; + } + /** * Measure the Duration of a single canary run, in seconds. * @@ -289,7 +341,9 @@ export class Canary extends cdk.Resource { /** * Returns a default role for the canary */ - private createDefaultRole(prefix?: string): iam.IRole { + private createDefaultRole(props: CanaryProps): iam.IRole { + const prefix = props.artifactsBucketLocation?.prefix; + // Created role will need these policies to run the Canary. // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-executionrolearn const policy = new iam.PolicyDocument({ @@ -318,11 +372,19 @@ export class Canary extends cdk.Resource { ], }); + const managedPolicies: iam.IManagedPolicy[] = []; + + if (props.vpc) { + // Policy that will have ENI creation permissions + managedPolicies.push(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')); + } + return new iam.Role(this, 'ServiceRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), inlinePolicies: { canaryPolicy: policy, }, + managedPolicies, }); } @@ -352,6 +414,15 @@ export class Canary extends cdk.Resource { }; } + private createRunConfig(props: CanaryProps): CfnCanary.RunConfigProperty | undefined { + if (!props.environmentVariables) { + return undefined; + } + return { + environmentVariables: props.environmentVariables, + }; + } + /** * Returns a canary schedule object */ @@ -362,12 +433,36 @@ export class Canary extends cdk.Resource { }; } - private createRunConfig(props: CanaryProps): CfnCanary.RunConfigProperty | undefined { - if (!props.environmentVariables) { + private createVpcConfig(props: CanaryProps): CfnCanary.VPCConfigProperty | undefined { + if (!props.vpc) { + if (props.vpcSubnets != null || props.securityGroups != null) { + throw new Error("You must provide the 'vpc' prop when using VPC-related properties."); + } + return undefined; } + + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); + if (subnetIds.length < 1) { + throw new Error('No matching subnets found in the VPC.'); + } + + let securityGroups: ec2.ISecurityGroup[]; + if (props.securityGroups && props.securityGroups.length > 0) { + securityGroups = props.securityGroups; + } else { + const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc, + description: 'Automatic security group for Canary ' + cdk.Names.uniqueId(this), + }); + securityGroups = [securityGroup]; + } + this._connections!.addSecurityGroup(...securityGroups); + return { - environmentVariables: props.environmentVariables, + vpcId: props.vpc.vpcId, + subnetIds, + securityGroupIds: cdk.Lazy.list({ produce: () => this.connections.securityGroups.map(sg => sg.securityGroupId) }), }; } diff --git a/packages/@aws-cdk/aws-synthetics/package.json b/packages/@aws-cdk/aws-synthetics/package.json index ed5b4eb9f4534..ac5406e1117eb 100644 --- a/packages/@aws-cdk/aws-synthetics/package.json +++ b/packages/@aws-cdk/aws-synthetics/package.json @@ -90,6 +90,7 @@ }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", @@ -98,6 +99,7 @@ }, "peerDependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-synthetics/test/canary.test.ts b/packages/@aws-cdk/aws-synthetics/test/canary.test.ts index 873bf36d61d6d..aee1ba78cd134 100644 --- a/packages/@aws-cdk/aws-synthetics/test/canary.test.ts +++ b/packages/@aws-cdk/aws-synthetics/test/canary.test.ts @@ -1,4 +1,5 @@ import { Match, Template } from '@aws-cdk/assertions'; +import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import { Duration, Lazy, Stack } from '@aws-cdk/core'; @@ -441,6 +442,124 @@ test('can specify custom test', () => { }); }); +describe('canary in a vpc', () => { + test('can specify vpc', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline(` + exports.handler = async () => { + console.log(\'hello world\'); + };`), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_2_0, + vpc, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Synthetics::Canary', { + Code: { + Handler: 'index.handler', + Script: ` + exports.handler = async () => { + console.log(\'hello world\'); + };`, + }, + VPCConfig: { + VpcId: { + Ref: Match.anyValue(), + }, + }, + }); + }); + + test('default security group and subnets', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline(` + exports.handler = async () => { + console.log(\'hello world\'); + };`), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_2_0, + vpc, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Synthetics::Canary', { + Code: { + Handler: 'index.handler', + Script: ` + exports.handler = async () => { + console.log(\'hello world\'); + };`, + }, + VPCConfig: { + VpcId: { + Ref: Match.anyValue(), + }, + SecurityGroupIds: Match.anyValue(), + SubnetIds: [...vpc.privateSubnets.map(subnet => ({ Ref: Match.stringLikeRegexp(subnet.node.id) }))], + }, + }); + }); + + test('provided security group', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC', { maxAzs: 2 }); + const sg = new ec2.SecurityGroup(stack, 'Sg', { vpc }); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline(` + exports.handler = async () => { + console.log(\'hello world\'); + };`), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_2_0, + vpc, + securityGroups: [sg], + }); + + // THEN + const template = Template.fromStack(stack); + const sgTemplate = template.findResources('AWS::EC2::SecurityGroup'); + const sgIds = Object.keys(sgTemplate); + + expect(sgIds).toHaveLength(1); + + template.hasResourceProperties('AWS::Synthetics::Canary', { + Code: { + Handler: 'index.handler', + Script: ` + exports.handler = async () => { + console.log(\'hello world\'); + };`, + }, + VPCConfig: { + VpcId: { + Ref: Match.anyValue(), + }, + SecurityGroupIds: [{ 'Fn::GetAtt': [sgIds[0], 'GroupId'] }], + }, + }); + }); +}); + test('Role policy generated as expected', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json deleted file mode 100644 index 256de95e7be25..0000000000000 --- a/packages/@aws-cdk/aws-synthetics/test/integ.asset.expected.json +++ /dev/null @@ -1,356 +0,0 @@ -{ - "Resources": { - "MyCanaryArtifactsBucket89975E6D": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "aws:kms" - } - } - ] - } - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, - "MyCanaryServiceRole593F9DD9": { - "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": "s3:ListAllMyBuckets", - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": "s3:GetBucketLocation", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "MyCanaryArtifactsBucket89975E6D", - "Arn" - ] - } - }, - { - "Action": "s3:PutObject", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyCanaryArtifactsBucket89975E6D", - "Arn" - ] - }, - "/*" - ] - ] - } - }, - { - "Action": "cloudwatch:PutMetricData", - "Condition": { - "StringEquals": { - "cloudwatch:namespace": "CloudWatchSynthetics" - } - }, - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "logs:CreateLogStream", - "logs:CreateLogGroup", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": "arn:aws:logs:::*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "canaryPolicy" - } - ] - } - }, - "MyCanary1A94CAFA": { - "Type": "AWS::Synthetics::Canary", - "Properties": { - "ArtifactS3Location": { - "Fn::Join": [ - "", - [ - "s3://", - { - "Ref": "MyCanaryArtifactsBucket89975E6D" - } - ] - ] - }, - "Code": { - "Handler": "canary.handler", - "S3Bucket": { - "Ref": "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3Bucket58589EB6" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3VersionKey8FF13E90" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3VersionKey8FF13E90" - } - ] - } - ] - } - ] - ] - } - }, - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "MyCanaryServiceRole593F9DD9", - "Arn" - ] - }, - "Name": "assetcanary-one", - "RuntimeVersion": "syn-nodejs-2.0", - "Schedule": { - "DurationInSeconds": "0", - "Expression": "rate(5 minutes)" - }, - "StartCanaryAfterCreation": true - } - }, - "MyCanaryTwoArtifactsBucket79B179B6": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "SSEAlgorithm": "aws:kms" - } - } - ] - } - }, - "UpdateReplacePolicy": "Retain", - "DeletionPolicy": "Retain" - }, - "MyCanaryTwoServiceRole041E85D4": { - "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": "s3:ListAllMyBuckets", - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": "s3:GetBucketLocation", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "MyCanaryArtifactsBucket89975E6D", - "Arn" - ] - } - }, - { - "Action": "s3:PutObject", - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyCanaryTwoArtifactsBucket79B179B6", - "Arn" - ] - }, - "/*" - ] - ] - } - }, - { - "Action": "cloudwatch:PutMetricData", - "Condition": { - "StringEquals": { - "cloudwatch:namespace": "CloudWatchSynthetics" - } - }, - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "logs:CreateLogStream", - "logs:CreateLogGroup", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": "arn:aws:logs:::*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "canaryPolicy" - } - ] - } - }, - "MyCanaryTwo6501D55F": { - "Type": "AWS::Synthetics::Canary", - "Properties": { - "ArtifactS3Location": { - "Fn::Join": [ - "", - [ - "s3://", - { - "Ref": "MyCanaryTwoArtifactsBucket79B179B6" - } - ] - ] - }, - "Code": { - "Handler": "canary.handler", - "S3Bucket": { - "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" - } - ] - } - ] - } - ] - ] - } - }, - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "MyCanaryTwoServiceRole041E85D4", - "Arn" - ] - }, - "Name": "assetcanary-two", - "RuntimeVersion": "syn-nodejs-2.0", - "Schedule": { - "DurationInSeconds": "0", - "Expression": "rate(5 minutes)" - }, - "StartCanaryAfterCreation": true - } - } - }, - "Parameters": { - "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3Bucket58589EB6": { - "Type": "String", - "Description": "S3 bucket for asset \"5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529b\"" - }, - "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bS3VersionKey8FF13E90": { - "Type": "String", - "Description": "S3 key for asset version \"5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529b\"" - }, - "AssetParameters5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529bArtifactHash74DCED3D": { - "Type": "String", - "Description": "Artifact hash for asset \"5bf46c83158ab3b336aba1449c21b02cbac2ccea621f17d842593bb39e3e529b\"" - }, - "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761": { - "Type": "String", - "Description": "S3 bucket for asset \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" - }, - "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B": { - "Type": "String", - "Description": "S3 key for asset version \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" - }, - "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820ArtifactHash536FDCC3": { - "Type": "String", - "Description": "Artifact hash for asset \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.vpc.expected.json b/packages/@aws-cdk/aws-synthetics/test/integ.vpc.expected.json new file mode 100644 index 0000000000000..612d33ebcd3ca --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/integ.vpc.expected.json @@ -0,0 +1,653 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc" + } + ] + } + }, + "MyVpcPublicSubnet1SubnetF6608456": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableC46AB2F4": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet1EIP096967CB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet2Subnet492B6BFB": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTable1DF17386": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTableAssociation227DE78D": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet2EIP8CCBA239": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet1Subnet5057CF7E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "MyVpcPrivateSubnet2Subnet0040C983": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableCEDCEECE": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableAssociation86A610DA": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + } + }, + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, + "MyVpcIGW5C4A4F63": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "canary-vpc/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "MyVpcCanaryArtifactsBucketDC69853C": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "MyVpcCanaryArtifactsBucketPolicy27806F2C": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "MyVpcCanaryArtifactsBucketDC69853C" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "MyVpcCanaryArtifactsBucketDC69853C", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyVpcCanaryArtifactsBucketDC69853C", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "MyVpcCanaryServiceRole2B1BBDE8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" + ] + ] + } + ], + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyVpcCanaryArtifactsBucketDC69853C", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyVpcCanaryArtifactsBucketDC69853C", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/cwsyn-*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ] + } + }, + "MyVpcCanarySecurityGroupEA9564AA": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatic security group for Canary canaryvpcMyVpcCanaryDEF63B1A", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + } + } + }, + "MyVpcCanary8C70C179": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "MyVpcCanaryArtifactsBucketDC69853C" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B" + } + ] + } + ] + } + ] + ] + } + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyVpcCanaryServiceRole2B1BBDE8", + "Arn" + ] + }, + "Name": "canary-vpc", + "RuntimeVersion": "syn-nodejs-puppeteer-3.3", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true, + "VPCConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "MyVpcCanarySecurityGroupEA9564AA", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + }, + { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + ], + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + } + } + } + } + }, + "Parameters": { + "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3Bucket705C3761": { + "Type": "String", + "Description": "S3 bucket for asset \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" + }, + "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820S3VersionKeyE546342B": { + "Type": "String", + "Description": "S3 key for asset version \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" + }, + "AssetParametersb1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820ArtifactHash536FDCC3": { + "Type": "String", + "Description": "Artifact hash for asset \"b1b777dcb79a2fa2790059927207d10bf5f4747d6dd1516e2780726d9d6fa820\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-synthetics/test/integ.vpc.ts b/packages/@aws-cdk/aws-synthetics/test/integ.vpc.ts new file mode 100644 index 0000000000000..e31f75817e68b --- /dev/null +++ b/packages/@aws-cdk/aws-synthetics/test/integ.vpc.ts @@ -0,0 +1,29 @@ +/// !cdk-integ canary-vpc + +import * as path from 'path'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as cdk from '@aws-cdk/core'; +import * as synthetics from '../lib'; + +/* + * Stack verification steps: + * + * -- aws synthetics get-canary --name canary-vpc has state of 'RUNNING' + * -- aws synthetics get-canary --name canary-vpc has VpcId + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'canary-vpc'); + +const vpc = new ec2.Vpc(stack, 'MyVpc', { maxAzs: 2 }); + +new synthetics.Canary(stack, 'MyVpcCanary', { + canaryName: 'canary-vpc', + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canary.zip')), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_3_3, + vpc, +}); + +app.synth(); diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 0f4e268dd3373..fb53a5c70f005 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -501,6 +501,7 @@ "./aws-iotthingsgraph": "./aws-iotthingsgraph/index.js", "./aws-iotwireless": "./aws-iotwireless/index.js", "./aws-ivs": "./aws-ivs/index.js", + "./aws-kafkaconnect": "./aws-kafkaconnect/index.js", "./aws-kendra": "./aws-kendra/index.js", "./aws-kinesis": "./aws-kinesis/index.js", "./aws-kinesisanalytics": "./aws-kinesisanalytics/index.js", @@ -540,6 +541,7 @@ "./aws-opsworks": "./aws-opsworks/index.js", "./aws-opsworkscm": "./aws-opsworkscm/index.js", "./aws-panorama": "./aws-panorama/index.js", + "./aws-personalize": "./aws-personalize/index.js", "./aws-pinpoint": "./aws-pinpoint/index.js", "./aws-pinpointemail": "./aws-pinpointemail/index.js", "./aws-qldb": "./aws-qldb/index.js", @@ -606,7 +608,8 @@ "./pipelines/.jsii": "./pipelines/.jsii", "./pipelines/.warnings.jsii.js": "./pipelines/.warnings.jsii.js", "./pipelines/lib/helpers-internal": "./pipelines/lib/helpers-internal/index.js", - "./region-info": "./region-info/index.js" + "./region-info": "./region-info/index.js", + "./triggers": "./triggers/index.js" }, "preferredCdkCliVersion": "2" }