Can we ping an AWS Lambda function ?

Context

Can we ping the ENI of an AWS Lambda function within the VPC ? While the answer can be obvious the path to get it was insightful for me and has helped me to be more comfortable with :

  • AWS IAM.
  • AWS Lambdas function.
  • AWS Static IP addresses and AWS default subnets.

This test can be performed with the AWS free tier. You will find the Terraform scripts here : Lambda Ping

I am a network engineer, I like diagram. Below you can find the diagram of the test.

What needs to be created

Infrastrusture:

  • aws_key_pair
  • aws_default_subnet
  • aws_security_group
  • aws_instance

Script:

  • A python script executed with the AWS lambda function

IAM:

  • aws_iam_role
  • aws_iam_role_policy_attachment
  • aws_iam_policy
  • aws_iam_policy_document

Lambda:

  • aws_lambda_function

This test is a good demonstration of one of the terraform benefits. Terraform is taking care of the dependencies and the order of creation between all resources. In the case of an imperative model for aws_key_pair and aws_instance you will need to explicitly state that aws_key_pair needs to be created before aws_instance is created. With the declarative approach of Terraform, you just declare the resources you want and Terraform is taking care of the execution order.

Resources creation

Infrastructure

aws_key
This is to push the public key of the computer that will be allowed to connect to the EC2 VM Instance.

resource "aws_key_pair" "deployer" {
	key_name   = "deployer-key"
	public_key = file("~/.ssh/id_rsa.pub")
}

aws_default_subnet
This resource can’t modify the default subnet address but it allows you to retrieve it from your default VPC.

resource "aws_default_subnet" "default_az1" {
  availability_zone = "us-east-2a"
}

aws_security_group
This will be the security group for the test. This security group will be assigned to the VM and the AWS lambda function to allow SSH to the VM, HTTP, ICMP between each other and outgoing traffic.

resource "aws_security_group" "sglambdaping" {
  name        = "sglambdaping"
  description = "Allow HTTP, ICMP and SSH traffic"

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "ALL_ICMP"
    from_port = -1
    to_port = -1
    protocol = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "terraform"
  }
}

aws_instance
The t2.micro VM instance. It will host one webserver to test that the ENI of the Lambda function works in our VPC by accessing it. This is also from this VM that we will try to ping the Lambda function’s ENI. I assign the VM to the default subnet of AZ1 which is normally 172.31.0.0/24. In the Terraform script I’m installing the webserver. I also assign a static IP address to be able to access the web server from the lambda function.

resource "aws_instance" "foo" {
  ami           = "ami-0443305dabd4be2bc"
  instance_type = "t2.micro"
  key_name      = aws_key_pair.deployer.key_name

  user_data     = <<-EOF
                  #!/bin/bash
                  sudo su
                  yum -y install httpd
                  echo "<p> My Instance! </p>" >> /var/www/html/index.html
                  sudo systemctl enable httpd
                  sudo systemctl start httpd
                  EOF

  vpc_security_group_ids = [
    aws_security_group.ubuntu.id
  ]

  private_ip = "172.31.0.8"
  subnet_id  = aws_default_subnet.default_az1.id
}

Python script

For the lambda part we first need a code that the function will execute. In this example it will be a HTTP request to our EC2 VM done with python urllib3 library.

#import json
import urllib3

print('Loading function')
def lambda_handler(event, context):
    http = urllib3.PoolManager()
    resp = http.request("GET", "http://172.31.0.8")
    print(resp.data)

Once the script is ready, you need to zip it then place the zip in the same directory as your Terraform script that will create the function. We then need to create the lambda resources.

IAM

In this post I explain in detail what the role, the assume_role_policy and the policy are used for.

First we load a default AWS Managed Policy that will allow the lambda function to create the ENI in the VPC.

aws_iam_policy

data "aws_iam_policy" "vpcpolicy" {
 name = "AWSLambdaVPCAccessExecutionRole"
}

We then create the role that will use this policy.

aws_iam_role

resource "aws_iam_role" "role-lambda" {
  name               = "nbolambdarole"
  assume_role_policy = data.aws_iam_policy_document.lambda_role.json
}

In this role we need to mention who will use it. In our case this is the lambda function. This action is done by defining a policy document where the principal is a lambda function.

aws_iam_policy_document

data "aws_iam_policy_document" "lambda_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

Then we assign this policy to the assume_role_policy of the role

  assume_role_policy = data.aws_iam_policy_document.lambda_role.json

The last step of the IAM configuration is to attach the role and the policy together.

aws_iam_role_policy_attachment

resource "aws_iam_role_policy_attachment" "role-policy-attach" {
  role       = aws_iam_role.role-lambda.name
  policy_arn = data.aws_iam_policy.vpcpolicy.arn
}

Lambda

Once the IAM part is done, we can create the lambda function. In our case we assign the previously created role to the function and the python script zip file.

aws_lambda_function

resource "aws_lambda_function" "lambda-get" {
  function_name = "nbo-tf-helloword-3"
  role          = aws_iam_role.role-lambda.arn
  filename         = "lambda_function.zip"
  source_code_hash = filebase64sha256("lambda_function.zip")
  runtime = "python3.9"
  handler = "lambda_function.lambda_handler"

  vpc_config {
      subnet_ids = [aws_default_subnet.default_az1.id]
      security_group_ids = [aws_security_group.sglambda.id]
  }
}
  • The source_code_hash is used to see if the lambda function has changed and then need to be updated.
  • The handler is normally the name of the python script dot the name of the python method you want to execute.
  • vpc_config is the VPC part of the lambda configuration to specify the security group associated to the lambda ENI and the subnet where the ENI will be.

The tests

Lambda will access the web server to validate the communication. To launch the test, we need to create an empty event that will be selected when we will click on the orange test button.

Click on the orange test button :) It should reply “My Instance”


After the communication is validated, we can connect to the EC2 VM instance to launch the ping command. It will fail to ping the Lambda function.

Challenges of this test

  • Understand the AWS IAM concept for the lambda function. The lambda function needs VPC rights to create the ENI in the VPC.
  • Find what default python library was available with AWS python to execute the HTTP request.
  • Understand how the lambda tests are done and what is a handler.

Related