DEV Community

Cover image for Deploying a Lambda with a static IP has never been so simple 🍰
Valentin BEGGI for AWS Community Builders

Posted on • Originally published at dev.to

Deploying a Lambda with a static IP has never been so simple 🍰

TL;DR

🧠 Learn how to deploy a Lambda with a static IP (for whitelisting concerns)
⚑ Perform NodeJS SFTP operations using this Lambda.


Your Serverless application might need to connect to a partner server that requires IP whitelisting.
We will use the Serverless Framework to deploy our Lambda function and the Typescript AWS CDK to create the infrastructure needed to give our Lambda a static IP πŸ”’.

If you are not familiar with these libraries, I recommend you check out this Ultimate Serverless DevX: Serverless Framework x CDK blog post to learn how to conciliate the two. 🀝

Moreover, we will use the NodeJS ssh2-promise package to perform SFTP operations in our Lambda function. πŸ’½

This is what we will build πŸ‘·:

Static Lambda IP Architecture

Let's get to work πŸ’ͺπŸ”¨!

1️⃣ Create a VPC hosted Lambda function with a static IP

☁️ VPC, NAT Gateway and Elastic IP

First, we need to create a VPC with a public subnet and a private subnet. The private subnet will host our Lambda function and the public subnet will host our NAT Gateway.

const vpc = new Vpc(stack, 'Vpc', {
    vpcName: 'vpc',
    natGateways: 1, // πŸ‘ˆ Automatically creates an Elastic IP
    maxAzs: 1, // πŸ‘ˆ Use more if you need high availability
    subnetConfiguration: [
        {
        name: 'private-subnet-1',
        subnetType: SubnetType.PRIVATE_WITH_NAT,
        cidrMask: 24,
        },
        {
        name: 'public-subnet-1',
        subnetType: SubnetType.PUBLIC,
        cidrMask: 24,
        },
    ],
});
Enter fullscreen mode Exit fullscreen mode

πŸ’Έ Pricing Disclaimer
This architecture will cost some money. The NAT Gateway runs on a EC2 instance which will cost you around 30$/month. This price is multiplied by the number of AZs you use.

I also recommend that you use CloudFormation Outputs to store the IDs of the resources you will need later on. We want to slap that ✨clean IaC code✨ in our PR πŸ–οΈ.


export const vpcSecurityGroupOutputId = 'SgOutputId';
export const vpcPrivateSubnetOutputId = 'VpcPvSubOutputId';
...

const privateSubnets = vpc.selectSubnets(
    { subnetType: SubnetType.PRIVATE_WITH_NAT }
);
export const [privateSubnetId1] = privateSubnets.subnetIds;

// πŸ‘‡ Outputing the IDs of the resources in case another stack needs them
new CfnOutput(stack, vpcSecurityGroupOutputId, {
    value: vpc.vpcDefaultSecurityGroup,
});
new CfnOutput(stack, vpcPrivateSubnet1OutputId, {
    value: privateSubnetId1,
});

Enter fullscreen mode Exit fullscreen mode

⚑ Deploy the Lambda function

Now that we have our VPC, we can deploy our Lambda function, using the Serverless Framework.

export const staticIpLambda = {
  timeout: 15,
  handler: getHandlerPath(__dirname),
  vpc: {
    securityGroupIds: [ stack.resolve(vpc.vpcDefaultSecurityGroup) ],
    subnetIds: [ stack.resolve(privateSubnetId1) ],
    },
};
Enter fullscreen mode Exit fullscreen mode

You can also check out this Serverless Framework x CDK article to learn how to leverage the stack.resolve() method πŸ§‘β€πŸš€.

And bam! πŸŽ‰ Our Lambda function is deployed in our private subnet.

All the outbound traffic from our Lambda function will now go through the NAT Gateway, which will use the Elastic IP we created earlier. πŸš€


You can retrieve the Elastic IP of your NAT Gateway using the AWS Console.
Go to the VPC service and click Elastic IPs. You should see the Elastic IP created by the CDK.

Elastic IP

Now just message your partner and ask them to whitelist this IP. πŸ“©

2️⃣ Perform SFTP operations in your Lambda function

❓ This part is actually optional.
We will cover a very specific SFTP use case and a problem I encountered while using the ssh2-promise and the serverless-esbuild plugin.

Now let's talk about a very specific use case: performing SFTP operations in your Lambda function.

I wanted to share with you this use case because I came across weird issues with the ssh2-promise package not being correctly bundled into my Lambda .zip code. 🀯

πŸ”’ SSH Key
If your SFTP partner requires a SSH Key, I recommend you to store the key in AWS Secrets Manager. You can then retrieve it in your Lambda function using Middy Secrets Manager middleware. It will provide the key as a string in your Lambda function context. πŸ€“

πŸ’½ Store the file you want to send in your Lambda's /tmp folder

The ssh2-promise package will need file system access to send your file. One way to achieve that in a Lambda function context is to leverage the /tmp folder.
This folder is writable and will be deleted when your Lambda function is terminated.

🚧 Warning
The /tmp folder is not persistent. If you want to keep track of the files you've sent you should also use S3. πŸ“¦
Also be aware that the /tmp folder is shared between successive Lambda invocations in the same execution environment. Use a unique file name to avoid any bugs πŸ›.

This is what the final code looks like with the ssh2-promise package:

fs.writeFileSync('/tmp/myFile.txt', 'Hello World!');

const sshPrivateKey = context["SSH_PRIVATE_KEY"]

const ssh = new SSH2Promise({
    host: 'sftpIpAddress',
    username: 'sftpUsername',
    privateKey: sshPrivateKey,
    port: "SFTP_PORT",
});

await ssh.connect();
const sftp = ssh.sftp();


await sftp.fastPut('/tmp/myFile.txt', "distant_name.txt");

await ssh.close();
Enter fullscreen mode Exit fullscreen mode

The catch 🎣

If like me you are using the serverless-esbuild plugin to bundle your Lambda function, you might encounter some weird issues.
The ssh2-promise package is not correctly bundled into your Lambda function. 🀯

To solve this issue, I first patched the serverless-esbuild plugin to allow yarn3 module bundling exclusion. I want the ssh2-promise package NOT to be bundled by esbuild.

The patch is available here as a github gist

Then, I had to add the ssh2-promise package to the externals section of my esbuild config.

// serverless.ts

const serverlessConfiguration = {
  ..., // πŸ‘ˆ Your other serverless config
  custom: {
    esbuild: { ...esbuildConfig, external: ['ssh2-promise'] },
  },
};
Enter fullscreen mode Exit fullscreen mode

Your Lambda function should now be able to perform SFTP operations. πŸš€


Thanks for reading! πŸ™

If you have any questions or feedback, feel free to reach out to me on Twitter or ask a question in the comments below. πŸ₯Έ

Also check out my Dev.to for more articles about AWS, Serverless and Cloud Development. πŸ“

Top comments (0)