-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ff18b20
commit e2aab8c
Showing
10 changed files
with
429 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* @skyleague/oss |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
name: tfsec | ||
on: push | ||
|
||
jobs: | ||
tfsec: | ||
uses: skyleague/node-standards/.github/workflows/reusable-tfsec.yml@main | ||
with: | ||
terraform-version: "1.2.7" | ||
working-directory: "./" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# SkyLeague `aws-lambda` - easy AWS Lambda deployments with Terraform | ||
|
||
[![tfsec](https://github.com/skyleague/aws-lambda/actions/workflows/tfsec.yml/badge.svg?branch=main)](https://github.com/skyleague/aws-lambda/actions/workflows/tfsec.yml) | ||
|
||
This module simplifies the deployment of AWS Lambda Functions using Terraform, as well as simplifying the adoption of the [Principle of Least Privilege](https://aws.amazon.com/blogs/security/techniques-for-writing-least-privilege-iam-policies/). When using this module, there is no need to attach AWS Managed Policies for basic functionality (CloudWatch logging, XRay tracing, VPC access). The Principle of Least Privilege is achieved by letting this module create a separate role for each Lambda Function. This role is granted the bare minimum set of permissions to match the configuration provided to this module. For example, `xray` permissions are automatically granted if (and only if) `xray_tracing_enabled = true`. Similar (dynamic) permissions are provided for other inputs (see [`iam.tf`](./iam.tf) for all dynamic permissions). Additional `existing_policy_arns` and `inline_policies` can be provided to grant the Lambda Function more permissions required by the application code (think of an S3 bucked or DynamoDB table used by your application code). | ||
|
||
## Usage | ||
|
||
```terraform | ||
module "this" { | ||
source = "[email protected]:skyleague/aws-lambda.git?ref=v1.0.0 | ||
function_name = "hello-world" | ||
local_artifact = { | ||
type = "dir" | ||
path = "${path.module}/.build/hello-world" | ||
s3_bucket = "my-artifact-bucket" | ||
s3_prefix = null | ||
} | ||
} | ||
``` | ||
|
||
## Options | ||
|
||
For a complete reference of all variables, have a look at the descriptions in [`variables.tf`](./variables.tf). | ||
|
||
## Outputs | ||
|
||
The module outputs the `lambda`, `log_group` and `role` as objects, providing the flexibility to extend the Lambda Function with additional functionality, and without limiting the set of exposed outputs. | ||
|
||
## Future additions | ||
|
||
This is the initial release of the module, with a very minimal set of standardized functionality. Most other functionality can already be achieved by utilizing the outputs, even the ones mentioned for standardization below. We plan on standardizing more integrations, so feel free to leave suggestions! Candidates include: | ||
|
||
- Event triggers with automatic Least Privilige permissions added to the Lambda role | ||
- ... **Your suggestions!** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# When s3_artifact is provided, fetch the metadata of the object | ||
data "aws_s3_object" "artifact" { | ||
count = var.s3_artifact == null ? 0 : 1 | ||
|
||
bucket = var.s3_artifact.bucket | ||
key = var.s3_artifact.key | ||
version_id = var.s3_artifact.version_id | ||
|
||
lifecycle { | ||
precondition { | ||
condition = var.local_artifact == null | ||
error_message = "Variable s3_artifact should not be provided when local_artifact is provided" | ||
} | ||
} | ||
} | ||
|
||
locals { | ||
s3_artifact_prefix = try( | ||
var.local_artifact.s3_prefix != null ? "${var.local_artifact.s3_prefix}/${var.function_name}" : var.function_name, | ||
var.function_name | ||
) | ||
} | ||
|
||
# When local_artifact is provided and the type is "dir", zip the contents of the directory | ||
data "archive_file" "artifact_dir" { | ||
count = var.local_artifact == null ? 0 : var.local_artifact.type == "dir" ? 1 : 0 | ||
|
||
type = "zip" | ||
source_dir = var.local_artifact.path | ||
output_path = "${path.module}/.artifacts/handler.zip" | ||
} | ||
# Upload the zipped artifact to S3 | ||
resource "aws_s3_object" "artifact_dir" { | ||
count = var.local_artifact == null ? 0 : var.local_artifact.type == "dir" ? 1 : 0 | ||
|
||
bucket = var.local_artifact.s3_bucket | ||
key = "${local.s3_artifact_prefix}/handler.zip" | ||
source = data.archive_file.artifact_dir[0].output_path | ||
source_hash = data.archive_file.artifact_dir[0].output_base64sha256 | ||
|
||
lifecycle { | ||
precondition { | ||
condition = var.s3_artifact == null | ||
error_message = "Variable local_artifact should not be provided when s3_artifact is provided" | ||
} | ||
} | ||
} | ||
|
||
# Upload the pre-existing artifact zip to S3 | ||
resource "aws_s3_object" "artifact_zip" { | ||
count = var.local_artifact == null ? 0 : var.local_artifact.type == "zip" ? 1 : 0 | ||
|
||
bucket = var.local_artifact.s3_bucket | ||
key = "${local.s3_artifact_prefix}/${basename(var.local_artifact.path)}" | ||
source = var.local_artifact.path | ||
source_hash = filebase64sha256(var.local_artifact.path) | ||
|
||
lifecycle { | ||
precondition { | ||
condition = var.s3_artifact == null | ||
error_message = "Variable local_artifact should not be provided when s3_artifact is provided" | ||
} | ||
} | ||
} | ||
|
||
locals { | ||
# Choose the correct artifact according to the input definition | ||
artifact = var.local_artifact == null ? data.aws_s3_object.artifact[0] : var.local_artifact.type == "zip" ? aws_s3_object.artifact_zip[0] : aws_s3_object.artifact_dir[0] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
data "aws_iam_policy_document" "assume_role" { | ||
statement { | ||
effect = "Allow" | ||
actions = ["sts:AssumeRole"] | ||
principals { | ||
type = "Service" | ||
identifiers = ["lambda.amazonaws.com"] | ||
} | ||
} | ||
} | ||
|
||
resource "aws_iam_role" "this" { | ||
path = "/lambda/" | ||
name_prefix = var.function_name | ||
|
||
assume_role_policy = data.aws_iam_policy_document.assume_role.json | ||
} | ||
|
||
data "aws_iam_policy_document" "this" { | ||
statement { | ||
effect = "Allow" | ||
actions = [ | ||
# We explicitly exclude the CreateLogGroup action | ||
# The log group is created by Terraform | ||
"logs:CreateLogStream", | ||
"logs:PutLogEvents" | ||
] | ||
|
||
#tfsec:ignore:aws-iam-no-policy-wildcards | ||
resources = ["${aws_cloudwatch_log_group.this.arn}:*", ] | ||
} | ||
|
||
dynamic "statement" { | ||
for_each = var.xray_tracing_enabled ? [true] : [] | ||
content { | ||
effect = "Allow" | ||
actions = [ | ||
"xray:PutTraceSegments", | ||
"xray:PutTelemetryRecords", | ||
] | ||
resources = ["*"] | ||
} | ||
} | ||
|
||
dynamic "statement" { | ||
for_each = var.vpc_config != null ? [true] : [] | ||
content { | ||
effect = "Allow" | ||
actions = [ | ||
"ec2:CreateNetworkInterface", | ||
"ec2:DescribeNetworkInterfaces", | ||
"ec2:DeleteNetworkInterface", | ||
"ec2:AssignPrivateIpAddresses", | ||
"ec2:UnassignPrivateIpAddresses", | ||
] | ||
resources = ["*"] | ||
} | ||
} | ||
|
||
dynamic "statement" { | ||
for_each = var.dead_letter_arn != null ? [var.dead_letter_arn] : [] | ||
content { | ||
effect = "Allow" | ||
actions = can(regex("^arn:aws:sqs", statement.value)) ? ["sqs:SendMessage"] : can(regex("^arn:aws:sns", statement.value)) ? ["sns:Publish"] : [] | ||
resources = [statement.value] | ||
} | ||
} | ||
|
||
dynamic "statement" { | ||
for_each = var.file_system_config != null ? [var.file_system_config] : [] | ||
content { | ||
effect = "Allow" | ||
actions = statement.value.read_only == false ? [ | ||
"elasticfilesystem:ClientMount", | ||
"elasticfilesystem:ClientWrite", | ||
] : ["elasticfilesystem:ClientMount"] | ||
resources = [statement.value.arn] | ||
} | ||
} | ||
} | ||
|
||
resource "aws_iam_role_policy" "this" { | ||
role = aws_iam_role.this.id | ||
name_prefix = "base" | ||
policy = data.aws_iam_policy_document.this.json | ||
} | ||
|
||
resource "aws_iam_role_policy" "inline_policies" { | ||
for_each = var.inline_policies | ||
|
||
role = aws_iam_role.this.id | ||
name_prefix = each.key | ||
policy = each.value.json | ||
} | ||
|
||
resource "aws_iam_role_policy_attachment" "existing_policies" { | ||
for_each = var.existing_policy_arns | ||
|
||
role = aws_iam_role.this.id | ||
policy_arn = each.value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
locals { | ||
environment = merge( | ||
can(regex("^nodejs", var.runtime)) ? { | ||
AWS_NODEJS_CONNECTION_REUSE_ENABLED = "1" | ||
} : {}, | ||
var.environment, | ||
) | ||
} | ||
|
||
resource "aws_lambda_function" "this" { | ||
function_name = var.function_name | ||
description = var.description | ||
|
||
handler = var.handler | ||
runtime = var.runtime | ||
memory_size = var.memory_size | ||
timeout = var.timeout | ||
|
||
architectures = var.graviton ? ["arm64"] : ["x86_64"] | ||
|
||
role = aws_iam_role.this.arn | ||
|
||
dynamic "environment" { | ||
for_each = length(keys(local.environment)) > 0 ? [local.environment] : [] | ||
content { | ||
variables = environment.value | ||
} | ||
} | ||
|
||
tracing_config { | ||
mode = var.xray_tracing_enabled ? "Active" : "Passive" | ||
} | ||
|
||
ephemeral_storage { | ||
size = var.ephemeral_storage | ||
} | ||
|
||
s3_bucket = local.artifact.bucket | ||
s3_key = local.artifact.key | ||
s3_object_version = local.artifact.version_id | ||
source_code_hash = coalesce(try(local.artifact.source_code_hash, null), base64encode(local.artifact.etag)) | ||
|
||
dynamic "dead_letter_config" { | ||
for_each = var.dead_letter_arn != null ? [var.dead_letter_arn] : [] | ||
content { | ||
target_arn = dead_letter_config.value | ||
} | ||
} | ||
|
||
dynamic "file_system_config" { | ||
for_each = var.file_system_config != null ? [var.file_system_config] : [] | ||
content { | ||
arn = file_system_config.value.arn | ||
local_mount_path = file_system_config.value.local_mount_path | ||
} | ||
} | ||
|
||
dynamic "vpc_config" { | ||
for_each = var.vpc_config != null ? [var.vpc_config] : [] | ||
content { | ||
security_group_ids = vpc_config.value.security_group_ids | ||
subnet_ids = vpc_config.value.subnet_ids | ||
} | ||
} | ||
|
||
lifecycle { | ||
precondition { | ||
condition = var.s3_artifact != null || var.local_artifact != null | ||
error_message = "Either local_artifact or s3_artifact is required." | ||
} | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
resource "aws_cloudwatch_log_group" "this" { | ||
name = "/aws/lambda/${var.function_name}" | ||
retention_in_days = var.log_retention_in_days | ||
kms_key_id = var.log_kms_key_id | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
output "lambda" { | ||
value = aws_lambda_function.this | ||
} | ||
|
||
output "log_group" { | ||
value = aws_cloudwatch_log_group.this | ||
} | ||
|
||
output "role" { | ||
value = aws_iam_role.this | ||
} |
Oops, something went wrong.