Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Terraform Lambda in VPC allow network access for HTTP Requests

I am using Terraform for the first time and got a little lost in the configuration and I am facing this issue with my Lambda Function that should be able to do a simple HTTP request to google.com or swapi.com ( or whatever other external API ).
In my case in my linkedin callback I need to use the linkedin api.
Here is some part of my configuration:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.21.0"
    }

    random = {
      source  = "hashicorp/random"
      version = ">= 3.3.0"
    }

    archive = {
      source  = "hashicorp/archive"
      version = ">= 2.2.0"
    }
  }

  required_version = ">= 1.0"
}


provider "aws" {
  region     = var.region
  access_key = var.access_key
  secret_key = var.secret_key
}

resource "aws_vpc" "main_vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "main-vpc"
  }

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_internet_gateway" "main_igw" {
  vpc_id = aws_vpc.main_vpc.id

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_route_table" "route-table-main" {
  vpc_id = aws_vpc.main_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main_igw.id
  }

  tags = {
    Name = "main-route-table"
  }

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_eip" "nat_eip" {
  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_nat_gateway" "nat_gateway" {
  allocation_id = aws_eip.nat_eip.id
  subnet_id     = aws_subnet.main_subnet_nat_gateway_a.id

  tags = {
    Name = "nat-gateway"
  }
}

resource "aws_route_table" "route-table-nat" {
  vpc_id = aws_vpc.main_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.nat_gateway.id
  }

  tags = {
    Name = "nat-route-table"
  }
}

resource "aws_security_group" "main_sg" {
  name        = "main-sg"
  description = "Terraform SG"
  vpc_id      = aws_vpc.main_vpc.id

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24" ]
  }

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

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

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_subnet" "main_subnet_a" {
  vpc_id            = aws_vpc.main_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-2a"

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_subnet" "main_subnet_b" {
  vpc_id            = aws_vpc.main_vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-2b"

  lifecycle {
    prevent_destroy = true
  }
}


resource "aws_subnet" "main_subnet_c" {
  vpc_id            = aws_vpc.main_vpc.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "us-east-2c"

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_subnet" "main_subnet_nat_gateway_a" {
  vpc_id            = aws_vpc.main_vpc.id
  cidr_block        = "10.0.4.0/24"
  availability_zone = "us-east-2a"
}

resource "aws_subnet" "main_subnet_nat_gateway_b" {
  vpc_id = aws_vpc.main_vpc.id
  cidr_block = "10.0.5.0/24"
  availability_zone = "us-east-2b"
}

resource "aws_subnet" "main_subnet_nat_gateway_c" {
  vpc_id = aws_vpc.main_vpc.id
  cidr_block = "10.0.6.0/24"
  availability_zone = "us-east-2c"
}


resource "aws_db_subnet_group" "db_subnet_group" {
  name        = "db_subnet_group"
  description = "My DB subnet group"

  subnet_ids = [
    aws_subnet.main_subnet_a.id,
    aws_subnet.main_subnet_b.id,
    aws_subnet.main_subnet_c.id
  ]

  lifecycle {
    prevent_destroy = true
  }
}


resource "aws_route_table_association" "subnet-association-a" {
  subnet_id      = aws_subnet.main_subnet_a.id
  route_table_id = aws_route_table.route-table-main.id

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_route_table_association" "subnet-association-b" {
  subnet_id      = aws_subnet.main_subnet_b.id
  route_table_id = aws_route_table.route-table-main.id

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_route_table_association" "subnet-association-c" {
  subnet_id      = aws_subnet.main_subnet_c.id
  route_table_id = aws_route_table.route-table-main.id

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_route_table_association" "subnet-association-nat-gateway-a" {
  subnet_id = aws_subnet.main_subnet_nat_gateway_a.id
  route_table_id = aws_route_table.route-table-nat.id
}


resource "aws_route_table_association" "subnet-association-nat-gateway-b" {
  subnet_id = aws_subnet.main_subnet_nat_gateway_b.id
  route_table_id = aws_route_table.route-table-nat.id
}

resource "aws_route_table_association" "subnet-association-nat-gateway-c" {
  subnet_id = aws_subnet.main_subnet_nat_gateway_c.id
  route_table_id = aws_route_table.route-table-nat.id
}

resource "aws_iam_role" "lambda_exec" {
  name               = "lambda-exec"
  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}


resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.lambda_exec.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

resource "aws_iam_role_policy_attachment" "lambda_sns_policy" {
  role       = aws_iam_role.lambda_exec.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSNSFullAccess"
}

data "archive_file" "lambda_auth" {
  type = "zip"

  source_dir  = "../${path.module}/lambdas/auth"
  output_path = "../${path.module}/lambdas/auth.zip"
}

resource "aws_s3_object" "lambda_auth" {
  bucket = aws_s3_bucket.lambda_bucket.id

  key    = "auth.zip"
  source = data.archive_file.lambda_auth.output_path

  etag = filemd5(data.archive_file.lambda_auth.output_path)
}

resource "aws_lambda_function" "auth_test" {
  function_name = "auth-test"

  s3_bucket = aws_s3_bucket.lambda_bucket.id
  s3_key    = aws_s3_object.lambda_auth.key

  runtime = "nodejs18.x"
  handler = "index.test"

  source_code_hash = data.archive_file.lambda_auth.output_base64sha256

  role = aws_iam_role.lambda_exec.arn

  environment {
    variables = {
      RDS_DATABASE_NAME = var.database_name
      RDS_DATABASE_HOST = aws_db_instance.cyrannus_database.address
      RDS_DATABASE_USER = aws_db_instance.cyrannus_database.username
      RDS_DATABASE_PASS = aws_db_instance.cyrannus_database.password
    }
  }

  vpc_config {
    security_group_ids = [aws_security_group.main_sg.id]
    subnet_ids         = [aws_subnet.main_subnet_nat_gateway_a.id, aws_subnet.main_subnet_nat_gateway_b.id, aws_subnet.main_subnet_nat_gateway_c.id]
  }

  timeout = 60
}

resource "aws_lambda_function" "auth_linkedin_callback" {
  function_name = "auth-linkedin-callback"

  s3_bucket = aws_s3_bucket.lambda_bucket.id
  s3_key    = aws_s3_object.lambda_auth.key

  runtime = "nodejs18.x"
  handler = "index.linkedinCallback"

  source_code_hash = data.archive_file.lambda_auth.output_base64sha256

  role = aws_iam_role.lambda_exec.arn

  environment {
    variables = {
      RDS_DATABASE_NAME = var.database_name
      RDS_DATABASE_HOST = aws_db_instance.cyrannus_database.address
      RDS_DATABASE_USER = aws_db_instance.cyrannus_database.username
      RDS_DATABASE_PASS = aws_db_instance.cyrannus_database.password
      LINKEDIN_CLIENT_ID = var.linkedin_client_id
      LINKEDIN_CLIENT_SECRET = var.linkedin_client_secret
      LINKEDIN_REDIRECT_URI = "${aws_apigatewayv2_stage.dev.invoke_url}/auth/linkedin/callback"
    }
  }

  vpc_config {
    security_group_ids = [aws_security_group.main_sg.id]
    subnet_ids         = [aws_subnet.main_subnet_nat_gateway_a.id, aws_subnet.main_subnet_nat_gateway_b.id, aws_subnet.main_subnet_nat_gateway_c.id]
  }

  timeout = 60
}


resource "aws_cloudwatch_log_group" "auth_test" {
  name = "/aws/lambda/${aws_lambda_function.auth_test.function_name}"

  retention_in_days = 14
}

resource "aws_cloudwatch_log_group" "auth_linkedin_callback" {
  name = "/aws/lambda/${aws_lambda_function.auth_linkedin_callback.function_name}"

  retention_in_days = 14
}

I’ve tried to use NAT Gateway as presented in some tutorial but I am a little lost, AWS Console interface seems to do a lot of things by default and I am not sure what I am missing.

I’ve managed for example to create an EC2 that is in the VPC to access a RDS that is in the same VPC and gave it network access with IGW and I am able to to curl commands an added a simple phpmyadmin.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

You have placed a NAT Gateway in a subnet that has a route to the NAT Gateway. That’s a circular reference. This subnet does not actually have a route to the Internet, so the NAT Gateway will not actually work.

  • Your NAT Gateway should be in a subnet that has a route to the Internet Gateway.
  • Your Lambda function should be in a subnet that has a route to the NAT Gateway.
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading