Introduction
As more and more organizations move to the cloud, developers are faced with new challenges of how to secure these new services. As this trend continues, you can expect to see hybrid models of some cloud and some on-premise. How you secure the tunnels for traditional architectures like VMs is quite straight forward, however, for the some of the newer serverless architecture patterns it can be quite difficult.
There are a few approaches you can take to secure these new technologies to communicate to on-premise infrastructure:
- Static IPs for Whitelisting
- SSH Tunnels
- VPNs
These approaches are fairly straight forward with VMs but with services like Lambdas where you can expect to see the IPs change a few times a week it can be very difficult to create predictable ranges that don't open all of AWS to your infrastructure.
In this article, I will show you how to setup Static IPs and in the future posts, we will dive into the other approaches.
Static IPs
This is one of the easier but also less secure options. Security is less about making impossible passwords and more about adding several layers. Each layer has its own vulnerabilities but combining a few together can improve your security posture. I recommend adding static IPs in addition to one of the other approaches.
In AWS, it's not the most straight forward thing to do since it requires a variety of resources for a VPC:
- Public Subnet
- Private Subnet
- NAT Gateway
- Elastic IP
- 2 Routes (public/private)
- Internet Gateway
Luckily, we can script all this with a CloudFormation:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation for VPC",
"Parameters": {
"env": {
"Type": "String"
}
},
"Resources": {
"VPCStaticIP": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "11.0.0.0/16",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"lambavpc"
]
]
}
}
]
}
},
"SubnetPublic": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "11.0.0.0/24",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"lambavpc",
"-",
"public-subnet"
]
]
}
}
],
"VpcId": {
"Ref": "VPCStaticIP"
}
}
},
"SubnetPrivate": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "11.0.1.0/24",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"lambavpc",
"-",
"private-subnet"
]
]
}
}
],
"VpcId": {
"Ref": "VPCStaticIP"
}
}
},
"InternetGateway": {
"Type": "AWS::EC2::InternetGateway",
"Properties": {
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"lambavpc",
"-",
"igw"
]
]
}
}
]
}
},
"VPCGatewayAttachment": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"InternetGatewayId": {
"Ref": "InternetGateway"
},
"VpcId": {
"Ref": "VPCStaticIP"
}
}
},
"RouteTablePublic": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "VPCStaticIP"
},
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"lambavpc",
"-",
"public-route"
]
]
}
}
]
}
},
"RoutePublic": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {
"Ref": "InternetGateway"
},
"RouteTableId": {
"Ref": "RouteTablePublic"
}
}
},
"SubnetRouteTableAssociationPublic": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"RouteTableId": {
"Ref": "RouteTablePublic"
},
"SubnetId": {
"Ref": "SubnetPublic"
}
}
},
"EIP": {
"Type": "AWS::EC2::EIP",
"Properties": {
"Domain": "vpc"
}
},
"NatGateway": {
"Type": "AWS::EC2::NatGateway",
"Properties": {
"AllocationId": {
"Fn::GetAtt": [
"EIP",
"AllocationId"
]
},
"SubnetId": {
"Ref": "SubnetPublic"
}
}
},
"RouteTablePrivate": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "VPCStaticIP"
},
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"lambavpc",
"-",
"private-route"
]
]
}
}
]
}
},
"RoutePrivate": {
"Type": "AWS::EC2::Route",
"Properties": {
"DestinationCidrBlock": "0.0.0.0/0",
"NatGatewayId": {
"Ref": "NatGateway"
},
"RouteTableId": {
"Ref": "RouteTablePrivate"
}
}
},
"SubnetRouteTableMainAssociationPrivate": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"RouteTableId": {
"Ref": "RouteTablePrivate"
},
"SubnetId": {
"Ref": "SubnetPrivate"
}
}
}
},
"Outputs": {}
}
This formation will create all the resources I mentioned above, next all you have to do is attach your Lambda to the new VPC network:
If you are using the AWS SDK you can also do this programmatically like this example in JavaScript:
const AWS = require('aws-sdk');
const lambda = new AWS.Lambda();
const result = await lambda
.createFunction({
Code: {
S3Bucket: 'BUCKET',
S3Key: 'KEY'
},
FunctionName: 'NAME',
Handler: 'index.main',
Publish: true,
Runtime: 'nodejs12.x',
VpcConfig: {
SecurityGroupIds: [
env.SG_ID
],
SubnetIds: [
env.SUBNET_ID1,
env.SUBNET_ID2
]
}
})
.promise();
We can test this by making a simple Lambda that will call a website that tells us our IP.
const axios = require('axios');
exports.handler = async () => {
const result = await axio.get('https://www.whatismyip.com/');
console.log('Result': result);
}
Recapping the above, this is not the most secure option but it is a layer you can add to make things more secure. When you combine this with other factors you can help harden your code so you don't have to worry about waking up at 2AM to fix an attack.
About Me
My name is Austin McDaniel, I'm the CTO and Co-founder of CRFT where we are setting out to disrupt how organizations automate security operations in the cloud.
Follow me on Twitter and Github for all things JavaScript, Cloud, Security and Data viz.
Top comments (0)