Open shantgup opened 1 year ago
Found a potential solution for this.
Instead of using the L1 construct CfnCustomResource , use the L2 construct AwsCustomResource which allows configuring several useful properties including vpc
and vpcSubnets
.
_Requesting a yellow-box note in the top-level description of the documentation for the L1 CfnCustomResource
construct here, that tells customers about the existence of the L2 construct._
In general, we recommend L2 constructs over L1 constructs whenever possible.
There absolutely should also be a way to define the default VPC for execution of lambdas in custom resources. Plausibly should be a feature flag.
Just a note for anyone who couldn't get the above code to work.
I found that a fair number (most?) of the custom lambda functions are not actually CfnFunction
s, and some are instead created using the very generic CfnResource
(eg. log-retention).
I used the following code to do the modification at the CfnResource
level, which catches every lambda in my stacks:
import { IConstruct } from "constructs";
import { CfnResource, IAspect } from "aws-cdk-lib";
import { aws_ec2 as EC2 } from "aws-cdk-lib";
/** VPC config for a `CfnResource` representing a lambda */
interface VpcConfig {
readonly securityGroupIds: string[];
readonly subnetIds: string[];
}
/**
* Tell us if the given construct is a lambda function.
*
* We look at the lowest level possible, which is the existence of a
* `CfnResource` with a `type` of `AWS::Lambda::Function`.
*/
const isLambda = (node: IConstruct): node is CfnResource => {
const isCfnResource = node instanceof CfnResource;
if (isCfnResource) {
return node.cfnResourceType === "AWS::Lambda::Function";
}
return false;
};
/**
* Recursively search through this node, returning all lambda functions we
* find.
*/
const getLambdaChildren = (node: IConstruct): CfnResource[] => {
if (isLambda(node)) {
return [node];
}
return node.node.children.flatMap((child) => getLambdaChildren(child));
};
export class LambdaVPCAspect implements IAspect {
private readonly vpc: EC2.Vpc;
private securityGroup?: EC2.ISecurityGroup;
constructor(vpc: EC2.Vpc) {
this.vpc = vpc;
}
public visit(node: IConstruct): void {
// Get all of the lambdas in this construct
const lambdas = getLambdaChildren(node);
lambdas.forEach((resource) => {
// Only apply if the lambda does not have it's own VPC config
// @ts-expect-error This is private but we need to see it...
const hasVpcConfig = !!resource.cfnProperties?.vpcConfig;
if (!hasVpcConfig) {
resource.addPropertyOverride("vpcConfig", this.getConfig(node));
}
});
}
/** Get the `VpcConfig` property for a lambda */
private getConfig(node: IConstruct): VpcConfig {
// Default security group
this.securityGroup =
this.securityGroup ??
EC2.SecurityGroup.fromSecurityGroupId(
node,
"lambdaAspectSecurityGroup",
this.vpc.vpcDefaultSecurityGroup
);
// Private subnets
const subnetIds = this.vpc.privateSubnets.map((x) => x.subnetId);
return {
securityGroupIds: [this.securityGroup.securityGroupId],
subnetIds: subnetIds,
};
}
}
It would be great to have a simpler way to do this, especially one without having to access a protected attribute 😬
@ollyfg thanks for this. A question I have is: how do you limit the security group to also allow AWS to reach the custom resource without opening it up to the whole internet?
If you have specific ingress/egress rules for IPs, the custom resource can't be reached. Not sure where this documentation is.
Said differently: can I limit the security group to the IP address that AWS uses to hit the custom resource?
Addendum, this is helpful. TLDR; use a VPC Endpoint instead of limiting IP addresses.
If you use the VPC endpoints feature, custom resources in the VPC must have access to CloudFormation-specific Amazon Simple Storage Service (Amazon S3) buckets. Custom resources must send responses to a presigned Amazon S3 URL. If they can't send responses to Amazon S3, CloudFormation won't receive a response and the stack operation fails. For more information, see Setting up VPC endpoints for AWS CloudFormation.
Resources:
Describe the feature
Creating this issue as requested.
Currently, there is no way to specify a VPC for Custom Resource Lambdas. The only solution is to use Aspects like below.
Requesting a feature that allows specifying
VpcId
or similar.There was a GitHub Issue about this in the past too, linked below. That issue has since been closed.
Thank you and please let me know if I can provide any other information.
Use Case
Need to specify my own custom VPC when creating custom resource Lambdas.
Proposed Solution
Allow a property like
VpcId
that lets users specify the VPC for custom resource Lambdas.Other Information
No response
Acknowledgements
CDK version used
n/a
Environment details (OS name and version, etc.)
n/a