When you’re working with EC2 or containers, VPCs have long been viewed as a must-have. Without them, you will face a constant barrage of attacks aimed at your infrastructure and OS, such as brute force login attacks.
But are VPCs still needed when it comes to AWS Lambda? What do you actually get when you put a function inside a VPC? And what are the downsides?
In this post, we will weigh up the pros and cons of putting Lambda functions inside a VPC and help you decide whether you need VPC or not.
When you create a Lambda function, you can give the function access to one of your VPCs (see below). Additionally, you need to specify what security groups and subnets to use for the VPC connection. These choices seem mundane at first, but they can have a big impact on performance and scalability.
For a function to access resources inside the designated VPC, it needs an Elastic Network Interface (ENI). ENIs are expensive and time-consuming to create, so AWS has implemented a number of optimizations.
Firstly, ENIs are shared across containers. If a function is allocated with 1GB of memory, then up to three (3) containers running this function can share the same ENI, as suggested by the following formula. More details are available here.
Idle containers are usually garbage collected after a few minutes. VPC-enabled functions are allowed to stay idle for longer to increase the likelihood that existing ENis are reused.
However, even with these optimizations, VPCs still impose serious scalability and performance limitations on Lambda functions.
By default, there is a soft limit of 350 ENIs per region. If this limit is reached then invocations of VPC-enabled functions will be throttled. Which is why you should always ask for a limit raise for ENIs whenever you ask for a concurrency raise for Lambda.
Each ENI consumes a private IP address from the associated subnet, and each subnet has a limited number of available IP addresses based on its CIDR block. It’s therefore possible to exhaust the available IP addresses in a subnet when Lambda functions scale up quickly. When this happens, Lambda would not be able to scale up the number of concurrent executions. This would impact other services such as EC2 or RDs, which also requires available IP addresses for ENIs. Therefore, it’s recommended that you create dedicated subnets with large IP ranges for your Lambda functions.
A VPC-enabled function would lose internet access because its ENI is only associated with a private IP address from the subnet. If your function needs to talk to other AWS services that are outside the VPC, such as DynamoDB or SNS, then it needs to have internet access. In this case, you need a NAT instance or an Amazon NAT Gateway, which adds additional cost and complexity.
It’s also worth mentioning that, as an alternative, you can use VPC endpoints to communicate with other AWS services instead. However, not every AWS service is supported, and VPC endpoints are also more complicated to set up and use.
The biggest drawback of VPC-enabled functions is that they suffer significantly longer cold starts. Because the process of ENIs is time-consuming and can often take up to 10s or more! One of my UK-based clients, SimplyBusiness, did some excellent analysis on the effect VPC has on Lambda cold starts.
Below is a comparison of the average cold start time for function inside and outside VPC. As the memory size increases, there is less ENI sharing based on the aforementioned formula. And the average cold start time increases as a result.
While ENI sharing does improve the average cold start time. It’s worth remembering that some invocations would still have a 10s cold start time whenever a new ENI is needed. Effectively, this still makes VPC-enabled functions unsuitable for any user-facing APIs.
Given all the challenges that VPCs introduce to Lambda, it’s no surprise that the official AWS recommendation is “Don’t put your Lambda function in a VPC unless you have to”!
But don’t you need VPCs to keep your functions secure?
Unlike EC2 instances, which need VPCs to shield them from malicious traffic. Lambda functions are protected by AWS Identity and Access Management (IAM) service, which provides both authentication and authorization. All incoming requests have to be signed with a valid AWS credential, and the caller must have the correct IAM permission.
This is the same mechanism that protects most other AWS services. If services such as SNS and DynamoDB are considered secure, even though they don’t run inside a VPC, then why shouldn’t Lambda functions be considered secure when they are protected by the same IAM service?
The EC2 instances that host our Lambda functions are not publicly accessible. It is therefore also not possible for attackers to compromise our functions by compromising the host. In fact, the entire EC2 fleet for Lambda was patched against Meltdown and Spectre shortly after their public disclosure. Which happened quietly behind the scenes while most of us were still busy patching our EC2 instances! It illustrates yet another benefit of letting your cloud provider take care of the security for the OS and networking. They can do a much better job than most of us are equipped to do ourselves.
VPCs allow you to control egress traffic using security groups and outbound rules. It is, therefore, possible to stop attackers from leaking sensitive data to the internet.
However, you rarely see egress filtering actually enforced in the real world. Therein lies a common fallacy with VPCs. They give you a false sense of confidence that attackers cannot get inside the VPC and therefore everything inside the VPC is given full trust.
This is of course not the case. Even though malicious ingress traffic can be stopped by IAM and VPC, it’s still possible to compromise Lambda functions via other means. For instance, it’s possible to compromise a function through its dependencies and steal sensitive data such as AWS credentials.
Which is why, with or without VPCs, you should monitor the egress traffic from your functions. PureSec’s FunctionShield library lets you block outbound internet connectivity, except those destined for other AWS resources. With FunctionShield, you can stop attackers from harvesting sensitive data from your function without resorting to VPCs.
So if VPCs don’t make your functions more secure, and they cause a lot of problems, when should you actually use them?
When you absolutely have to. That is, if your function needs to access a resource that runs in the VPC, for example:
Here is the decision flow from the official AWS best practices guide for working with Lambda.
In summary, VPCs don’t provide the same level of security to Lambda as they do for EC2. Because Lambda functions are already secured by IAM, which provides both authentication and authorization. IAM stops malicious ingress traffic, but you still need to protect against malicious egress traffic. You can use tools such as FunctionShield to block connectivity to public internet without resorting to VPCs, which helps you stop attackers from stealing sensitive data from compromised functions.
On the flip side, VPCs introduce a host of challenges to Lambda:
Overall, I agree with AWS’s official recommendation that you should stay away from VPCs as much as possible. Until the underlying dependencies on ENIs are resolved, it just doesn’t make sense to use VPCs with Lambda. Unless you absolutely have to, because you need to access resources that are only accessible via the VPC.