-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Changes from 4 commits
80cbb5b
432eea0
0c23ec0
c293aa5
e7941a0
cde330c
12d2d10
303cb7e
8b8ac53
7ca0270
c2ef535
daa317f
2b0778c
48c0ebc
2d0a396
d543203
343ee82
6449e13
8e38971
adb0f98
1445e2e
5eeefae
c29d7a6
2a38371
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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'; | ||
|
@@ -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 | ||
*/ | ||
|
@@ -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; | ||
} | ||
|
||
/** | ||
|
@@ -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; | ||
|
@@ -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({ | ||
|
@@ -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, | ||
}); | ||
} | ||
|
||
|
@@ -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 | ||
|
||
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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should pull the list of security groups from securityGroupIds: Lazy.listValue({ produce: () => this.connections.securityGroups.map(sg => sg.securityGroupId) }), There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @corymhall Done! Though used |
||
}; | ||
} | ||
|
||
|
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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.