Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.terraform*
/_test/.terraform
/_test/.terraform.lock.hcl
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v1.3.0

- [Add VPC support](https://github.com/babbel/terraform-aws-lambda-with-inline-code/pull/19)

## v1.2.1

- [Empty environment is handled correctly](https://github.com/babbel/terraform-aws-lambda-with-inline-code/pull/15)
Expand Down
111 changes: 111 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ resource "aws_lambda_function" "this" {

role = aws_iam_role.this.arn

dynamic "vpc_config" {
for_each = local.vpc_configs

content {
subnet_ids = vpc_config.value.subnets[*].id
security_group_ids = values(aws_security_group.this)[*].id
}
}

dynamic "environment" {
// local.environments is built using a merge, and merges always result in a map
// so we can safely assume we're dealing with a map here.
Expand Down Expand Up @@ -41,6 +50,8 @@ data "archive_file" "this" {
output_path = ".terraform/tmp/lambda/${var.function_name}.zip"
}

# IAM role

resource "aws_iam_role" "this" {
name = "lambda-${var.function_name}"

Expand All @@ -60,6 +71,8 @@ data "aws_iam_policy_document" "lambda-assume-role" {
}
}

# CloudWatch Logs group

resource "aws_iam_role_policy" "cloudwatch-log-group" {
role = aws_iam_role.this.name
name = "cloudwatch-log-group"
Expand All @@ -86,6 +99,104 @@ data "aws_iam_policy_document" "cloudwatch-log-group" {
}
}

# VPC config

locals {
# convert `var.vpc_config` into a `for_each`-compatible local
vpc_config_key = "lambda"
vpc_configs = var.vpc_config != null ? { (local.vpc_config_key) = var.vpc_config } : {}
}

resource "aws_security_group" "this" {
for_each = local.vpc_configs

name = "lambda-${var.function_name}"
description = "Lambda: ${var.function_name}"
vpc_id = each.value.vpc.id

tags = merge({
Name = "Lambda: ${var.function_name}"
}, var.tags)

lifecycle {
create_before_destroy = true
}
}

resource "aws_security_group_rule" "egress" {
for_each = aws_security_group.this

security_group_id = each.value.id

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

lifecycle {
create_before_destroy = true
}
}

data "aws_iam_policy_document" "vpc" {
for_each = local.vpc_configs

statement {
actions = ["ec2:CreateNetworkInterface"]

resources = flatten([
"arn:${data.aws_partition.current[local.vpc_config_key].partition}:ec2:${data.aws_region.current[local.vpc_config_key].name}:${data.aws_caller_identity.current[local.vpc_config_key].account_id}:network-interface/*",
each.value.subnets[*].arn,
values(aws_security_group.this)[*].arn
])
}

statement {
actions = ["ec2:DescribeNetworkInterfaces"]
resources = ["*"]

condition {
variable = "ec2:Region"
test = "StringEquals"
values = [data.aws_region.current[local.vpc_config_key].name]
}
}

statement {
# It is not possible to restrict this permissions because the Lambda runtime
# is making a DryRun call to this action without any request parameters
# before actually creating the Lambda function.
actions = ["ec2:DeleteNetworkInterface"]
resources = ["*"]

condition {
variable = "ec2:Region"
test = "StringEquals"
values = [data.aws_region.current[local.vpc_config_key].name]
}
}
}

resource "aws_iam_role_policy" "vpc" {
for_each = data.aws_iam_policy_document.vpc

role = aws_iam_role.this.name
name = "vpc"
policy = each.value.json
}

data "aws_partition" "current" {
for_each = local.vpc_configs
}

data "aws_region" "current" {
for_each = local.vpc_configs
}

data "aws_caller_identity" "current" {
for_each = local.vpc_configs
}

# Secret environment variables

Expand Down
6 changes: 6 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ output "iam_role" {

description = "The IAM role the Lambda function will assume."
}

output "security_group" {
value = lookup(aws_security_group.this, local.vpc_config_key, null)

description = "The VPC security group the Lambda function will use if `var.vpc_config` is specified; `null` otherwise."
}
26 changes: 26 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,29 @@ variable "timeout" {

description = "The amount of time (in seconds) per execution before stopping it."
}

variable "vpc_config" {
type = object({
subnets = list(
object({
arn = string
id = string
})
)

vpc = object({
id = string
})
})

default = null

description = <<EOS
VPC configuration of the Lambda function:

* `subnets`: List of subnets in which the Lambda function will be running. The subnets must be in the VPC specified by `vpc`.
* `vpc`: The VPC in which the Lambda function will be running and where all VPC-related resources (e.g. the security group) will be located.

If `vpc_config` is `null` the Lambda function will not be placed into a VPC.
EOS
}