DEV Community

se-piyush
se-piyush

Posted on

Setting Up VPC and Lambda Function with Terraform

In my previous post, Setting Up a VPC for Your App Using AWS Management Console, I talked about how to set up a VPC using the AWS Management Console.

In this blog, I will show you how to achieve a similar setup using Terraform. I have a Lambda function that is behind a custom domain API Gateway. This Lambda function interacts with DynamoDB (without going through the internet) and some third-party APIs.

Below is the Terraform code to set up the required infrastructure.

Terraform Code

main.tf File

resource "aws_vpc" "main" { cidr_block = "<YOUR_PREFERRED_IP_CIDR_BLOCK>" } # Create private subnets resource "aws_subnet" "private" { count = <NUMBER_OF_SUBNETS_YOU_WANT> vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index * 2 + 1) availability_zone = element(data.aws_availability_zones.available.names, count.index) map_public_ip_on_launch = false } # Create public subnets resource "aws_subnet" "public" { count = <NUMBER_OF_SUBNETS_YOU_WANT> vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index * 2) availability_zone = element(data.aws_availability_zones.available.names, count.index) map_public_ip_on_launch = true } # Create Internet Gateway resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.main.id } # Create Elastic IP for NAT Gateway resource "aws_eip" "nat" { domain = "vpc" } # Create NAT Gateway resource "aws_nat_gateway" "nat" { allocation_id = aws_eip.nat.id subnet_id = element(aws_subnet.public.*.id, 0) } # Create public route table resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } } # Associate public subnets with public route table resource "aws_route_table_association" "public" { count = length(aws_subnet.public.*.id) subnet_id = element(aws_subnet.public.*.id, count.index) route_table_id = aws_route_table.public.id } # Create private route table resource "aws_route_table" "private" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.nat.id } } # Associate private subnets with private route table resource "aws_route_table_association" "private" { count = length(aws_subnet.private.*.id) subnet_id = element(aws_subnet.private.*.id, count.index) route_table_id = aws_route_table.private.id } # Create security group for Lambda resource "aws_security_group" "lambda" { name = "lambda-sg" description = "Security group for Lambda" vpc_id = aws_vpc.main.id egress { from_port = 443 to_port = 443 protocol = "TCP" cidr_blocks = [aws_vpc.main.cidr_block] } } # Lambda VPC Endpoint for DynamoDB resource "aws_vpc_endpoint" "dynamodb" { vpc_id = aws_vpc.main.id service_name = "com.amazonaws.${var.region}.dynamodb" vpc_endpoint_type = "Gateway" route_table_ids = aws_route_table.private[*].id } # The above terraform command automatically creates prefix list in the private route table. # Lambda Function resource "aws_lambda_function" "lambda_function" { filename = <PATH_TO_YOUR_ZIP_FILE.zip> function_name = "<LAMBDA_FUNCTION_NAME>" role = aws_iam_role.lambda_role.arn handler = "index.handler" runtime = "nodejs20.x" memory_size = 512 timeout = 10 logging_config { log_format = "Text" log_group = "/aws/lambda/<LAMBDA_FUNCTION_NAME>" } tracing_config { mode = "PassThrough" } ephemeral_storage { size = "512" } vpc_config { subnet_ids = aws_subnet.private[*].id security_group_ids = [aws_security_group.lambda.id] } } resource "aws_lambda_alias" "lambda_alias" { name = var.stage function_name = aws_lambda_function.lambda_function.function_name function_version = aws_lambda_function.lambda_function.version } resource "aws_lambda_permission" "apigw" { statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = aws_lambda_function.lambda_function.function_name principal = "apigateway.amazonaws.com" source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/${var.stage}/*" } resource "aws_iam_role" "lambda_role" { name = "lambdaRole" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } } ] }) } # DynamoDB Tables resource "aws_dynamodb_table" "dynamo_table" { // YOUR TABLE CONFIG } resource "aws_iam_role_policy" "lambda_policy" { name = "lambda-policy" role = aws_iam_role.lambda_role.id policy = jsonencode({ Version = "2012-10-17", Statement = [ { Effect = "Allow", Action = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:TagResource", "logs:PutLogEvents" ], Resource = [ "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/<LAMBDA_FUNCTION_NAME>-${var.stage}*:*" ] }, { Effect = "Allow", Action = [ "logs:PutLogEvents" ], Resource = [ "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/<LAMBDA_FUNCTION_NAME>-${var.stage}*:*:*" ] }, { Effect = "Allow", Action = [ "dynamodb:Query", "dynamodb:Scan", "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem" ], Resource = [ aws_dynamodb_table.dynamo_table.arn ] }, { Effect = "Allow", Action = [ "ec2:CreateNetworkInterface", "ec2:DescribeNetworkInterfaces", "ec2:DeleteNetworkInterface", "ec2:DescribeInstances", "ec2:AttachNetworkInterface" ], Resource = "*" } ] }) } resource "aws_iam_role_policy_attachment" "policy_attach" { role = aws_iam_role.lambda_role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } # API Gateway resource "aws_api_gateway_rest_api" "api" { name = aws_lambda_function.lambda_function.function_name description = "API for your Service in ${var.stage} environment" } resource "aws_api_gateway_resource" "proxy" { rest_api_id = aws_api_gateway_rest_api.api.id parent_id = aws_api_gateway_rest_api.api.root_resource_id path_part = "{proxy+}" } resource "aws_api_gateway_method" "proxy_method" { rest_api_id = aws_api_gateway_rest_api.api.id resource_id = aws_api_gateway_resource.proxy.id http_method = "ANY" authorization = "NONE" } resource "aws_api_gateway_integration" "proxy_integration" { rest_api_id = aws_api_gateway_rest_api.api.id resource_id = aws_api_gateway_resource.proxy.id http_method = aws_api_gateway_method.proxy_method.http_method type = "AWS_PROXY" integration_http_method = "POST" uri = aws_lambda_function.lambda_function.invoke_arn } resource "aws_api_gateway_deployment" "api_deployment" { depends_on = [ aws_api_gateway_method.proxy_method, aws_api_gateway_integration.proxy_integration, ] rest_api_id = aws_api_gateway_rest_api.api.id lifecycle { create_before_destroy = true } } resource "aws_api_gateway_stage" "api_stage" { stage_name = var.stage rest_api_id = aws _api_gateway_rest_api.api.id deployment_id = aws_api_gateway_deployment.api_deployment.id } resource "aws_api_gateway_base_path_mapping" "base_path_mapping" { depends_on = [aws_api_gateway_stage.api_stage] domain_name = var.api_gateway_domain_name api_id = aws_api_gateway_rest_api.api.id stage_name = aws_api_gateway_stage.api_stage.stage_name base_path = var.base_path } data "aws_caller_identity" "current" {} 
Enter fullscreen mode Exit fullscreen mode

Notice the use of subnet and security group while creating the Lambda function.

Note: If you are using VPC Endpoint of type Gateway than you dont need to make any changes in your code specially in dynamo db configurations.

Conclusion

By following these steps and utilizing the Terraform code provided, you can set up a secure and efficient environment for your application on AWS. This setup involves creating and configuring a VPC, subnets, route tables, internet gateway, NAT gateway, VPC endpoints, and associating them appropriately. Following these steps will help you establish a secure and efficient environment for your application on AWS.

Top comments (0)