Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(synthetics): add vpc configuration #18447

Merged
merged 24 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
80cbb5b
feat(aws-synthetics): complete runConfig and add vpcConfig
RichiCoder1 Jan 14, 2022
432eea0
update tests and integ commit
RichiCoder1 Jan 15, 2022
0c23ec0
remove cdk.out template stuff
RichiCoder1 Jan 16, 2022
c293aa5
Merge branch 'aws:master' into aws-synthetic-runconfig-and-vpc
RichiCoder1 Jan 16, 2022
e7941a0
Merge branch 'master' into aws-synthetic-runconfig-and-vpc
RichiCoder1 Feb 25, 2022
cde330c
remove runconfig changes, scope down to vpc
RichiCoder1 Feb 25, 2022
12d2d10
revert runconfig related canary changes
RichiCoder1 Feb 25, 2022
303cb7e
add readme docs for vpc
RichiCoder1 Feb 25, 2022
8b8ac53
fixup tests and docs
RichiCoder1 Feb 25, 2022
7ca0270
include integration fixes
RichiCoder1 Feb 25, 2022
c2ef535
fix docs rosetta compilation
RichiCoder1 Feb 25, 2022
daa317f
Apply suggestions from code review
RichiCoder1 Mar 4, 2022
2b0778c
fix docs?
RichiCoder1 Mar 5, 2022
48c0ebc
merge
RichiCoder1 Mar 5, 2022
2d0a396
address pr feedback around vpc and split integ tests
RichiCoder1 Mar 5, 2022
d543203
simplify subnet verification logic
RichiCoder1 Mar 5, 2022
343ee82
address pr comments and expandd unit tests
RichiCoder1 Mar 11, 2022
6449e13
Merge branch 'master' into aws-synthetic-runconfig-and-vpc
kaizencc Mar 11, 2022
8e38971
remove private subnet warning and add IConnectable
RichiCoder1 Mar 14, 2022
adb0f98
tweak to only init connections if vpc, like lambda
RichiCoder1 Mar 14, 2022
1445e2e
further tweak wording
RichiCoder1 Mar 14, 2022
5eeefae
make sg ids be evaluated at render time
RichiCoder1 Mar 15, 2022
c29d7a6
Merge branch 'master' into aws-synthetic-runconfig-and-vpc
kaizencc Mar 15, 2022
2a38371
Merge branch 'master' into aws-synthetic-runconfig-and-vpc
mergify[bot] Mar 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 130 additions & 7 deletions packages/@aws-cdk/aws-synthetics/lib/canary.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -83,6 +84,33 @@ export interface ArtifactsBucketLocation {
readonly prefix?: string;
}

/**
* Properties for specifying the VPC to place a canary in
*/
export interface VpcConfiguration {
/**
* The VPC where this canary is run.
*
* Specify this if the canary needs to access resources in a VPC.
*/
readonly vpc: ec2.IVpc;

/**
* Where to place the network interfaces within the VPC.
*
* @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.
*
* @default - If the function 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[];
}

/**
* Properties for a canary
*/
Expand Down Expand Up @@ -179,6 +207,45 @@ export interface CanaryProps {
* @default - No environment variables.
*/
readonly environmentVariables?: { [key: string]: string };

/**
* How long the canary is allowed to run before it must stop.
* You can't set this time to be longer than the frequency of the runs of this canary.
* If you omit this field, the frequency of the canary is used as this value, up to a maximum of 900 seconds.
*
* @default cdk.Duration.seconds(840)
*/
readonly timeout?: cdk.Duration;

/**
* The maximum amount of memory that the canary can use while running. This value
* must be a multiple of 64. The range is 960 to 3008.
*
* @default cdk.Size.mebibytes(960)
*/
readonly memorySize?: cdk.Size;

/**
* Specifies whether this canary is to use active AWS X-Ray tracing when it runs.
*
* Active tracing enables this canary run to be displayed in the ServiceLens and X-Ray service maps
* even if the canary does not hit an endpoint that has X-ray tracing enabled.
*
* You can enable active tracing only for canaries that use version syn-nodejs-2.0 or later for their canary runtime.
*
* Enabling tracing increases canary run time by 2.5% to 7%.
*
* @default false
*/
readonly tracing?: boolean;

/**
* If this canary is to test an endpoint in a VPC, specify this to place a canary in
* a VPC.
*
* @default - No VPC
*/
readonly vpcConfig?: VpcConfiguration;
}

/**
Expand Down Expand Up @@ -229,19 +296,22 @@ export class Canary extends cdk.Resource {
enforceSSL: true,
});

this.role = props.role ?? this.createDefaultRole(props.artifactsBucketLocation?.prefix);
this.role = props.role ?? this.createDefaultRole(props);

const schedule = this.createSchedule(props);

const resource: CfnCanary = new CfnCanary(this, 'Resource', {
artifactS3Location: this.artifactsBucket.s3UrlForObject(props.artifactsBucketLocation?.prefix),
executionRoleArn: this.role.roleArn,
startCanaryAfterCreation: props.startAfterCreation ?? true,
runtimeVersion: props.runtime.name,
name: this.physicalName,
schedule: this.createSchedule(props),
schedule,
failureRetentionPeriod: props.failureRetentionPeriod?.toDays(),
successRetentionPeriod: props.successRetentionPeriod?.toDays(),
code: this.createCode(props),
runConfig: this.createRunConfig(props),
runConfig: this.createRunConfig(props, schedule),
vpcConfig: this.createVpcConfig(props.vpcConfig),
});

this.canaryId = resource.attrId;
Expand Down Expand Up @@ -289,8 +359,10 @@ 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 { partition } = cdk.Stack.of(this);
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({
Expand All @@ -315,11 +387,28 @@ export class Canary extends cdk.Resource {
],
});

if (props.tracing) {
policy.addStatements(
new iam.PolicyStatement({
resources: ['*'],
actions: ['xray:PutTraceSegments'],
}),
);
}

const managedPolicies: iam.IManagedPolicy[] = [];

if (props.vpcConfig) {
// 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,
});
}

Expand Down Expand Up @@ -350,12 +439,46 @@ export class Canary extends cdk.Resource {
};
}

private createRunConfig(props: CanaryProps): CfnCanary.RunConfigProperty | undefined {
if (!props.environmentVariables) {
private createRunConfig(props: CanaryProps, schedule: CfnCanary.ScheduleProperty): CfnCanary.RunConfigProperty | undefined {
// Cloudformation implementation made TimeoutInSeconds a required field where it should not (see links below).
// So here is a workaround to fix https://github.com/aws/aws-cdk/issues/9300 and still follow documented behavior.
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-synthetics-canary-runconfig.html#cfn-synthetics-canary-runconfig-timeoutinseconds
// https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-synthetics/issues/31

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should fail synth if a user tries to set vpc related options without setting vpc. Right now they look to be just ignored in that case, which might be cause for confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the two places I took for inspiration don't explicitly do this, but I think it's a good idea.

const MAX_CANARY_TIMEOUT = 840;
const rateDuration = Schedule.expressionToRateDuration(schedule.expression);
const canaryTimeout = rateDuration.toSeconds() <= MAX_CANARY_TIMEOUT ? rateDuration.toSeconds() : MAX_CANARY_TIMEOUT;

return {
timeoutInSeconds: props.timeout?.toSeconds() ?? canaryTimeout,
activeTracing: props.tracing,
environmentVariables: props.environmentVariables,
memoryInMb: props.memorySize?.toMebibytes(),
};
}

private createVpcConfig(props: VpcConfiguration | undefined): CfnCanary.VPCConfigProperty | undefined {
RichiCoder1 marked this conversation as resolved.
Show resolved Hide resolved
if (!props) {
return undefined;
}

const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets);
let securityGroups: ec2.ISecurityGroup[];

if (props.securityGroups && props.securityGroups.length > 0) {
securityGroups = props.securityGroups;
kaizencc marked this conversation as resolved.
Show resolved Hide resolved
} else {
const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {
vpc: props.vpc,
description: 'Automatic security group for Canary ' + cdk.Names.uniqueId(this),
});
securityGroups = [securityGroup];
}

return {
environmentVariables: props.environmentVariables,
vpcId: props.vpc.vpcId,
subnetIds,
securityGroupIds: securityGroups.map(sg => sg.securityGroupId),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pull the list of security groups from this.connections since users will be able to modify this connections object.

securityGroupIds: Lazy.listValue({ produce: () => this.connections.securityGroups.map(sg => sg.securityGroupId) }),

Copy link
Contributor Author

@RichiCoder1 RichiCoder1 Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corymhall Done! Though used Lazy.list as I got a warning that listValue was deprecated and that seemed to be the replacement. Good to know too, I was wondering how this sort of lazy evaluation worked!

};
}

Expand Down
39 changes: 39 additions & 0 deletions packages/@aws-cdk/aws-synthetics/lib/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,45 @@ export class Schedule {
return new Schedule(`cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`);
}

/**
* Convert a Schedule expression's minutes in to a Duration.
*
* @param expression Schedule expression such as 'rate(2 minutes)'
*/
public static expressionToRateDuration(expression: string): Duration {
if (expression.includes('cron')) {
RichiCoder1 marked this conversation as resolved.
Show resolved Hide resolved
const intervals = expression.replace(/cron\(/, '').replace(/\)/, '');
const [minuteExpression] = intervals.split(' ');
if (minuteExpression.includes('/')) {
const [, every] = minuteExpression.split('/');
return Duration.minutes(Number(every));
} else if (minuteExpression.includes('*')) {
return Duration.hours(1);
} else {
return Duration.minutes(Number(minuteExpression));
}
} else {
const interval = expression.replace(/rate\(/, '').replace(/\)/, '');
const [value, period] = interval.split(' ');
const number = Number(period ? value : '0');
const unit = period ? period : 'minutes';

switch (unit) {
case 'second':
case 'seconds':
return Duration.seconds(number);
case 'minute':
case 'minutes':
return Duration.minutes(number);
case 'hour':
case 'hours':
return Duration.hours(number);
default:
throw new Error('Unit not supported');
}
}
}

private constructor(
/**
* The Schedule expression
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-synthetics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Loading