Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
## v1.0.0

- [Initial version](https://github.com/babbel/terraform-aws-lambda-with-inline-code/pull/1)

## v1.1.0

- [Added support for secret environment variables](https://github.com/babbel/terraform-aws-lambda-with-inline-code/pull/6)
78 changes: 76 additions & 2 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ resource "aws_lambda_function" "this" {
timeout = var.timeout
reserved_concurrent_executions = var.reserved_concurrent_executions

layers = local.secrets_wrapper_layers

role = aws_iam_role.this.arn

dynamic "environment" {
for_each = var.environment_variables != null ? [{ variables = var.environment_variables }] : []
for_each = local.environment_variables != null ? [{ variables = local.environment_variables }] : []

content {
variables = environment.value.variables
Expand All @@ -23,7 +25,10 @@ resource "aws_lambda_function" "this" {

tags = var.tags

depends_on = [aws_cloudwatch_log_group.this]
depends_on = [
aws_cloudwatch_log_group.this,
null_resource.watch_iam_role_policy_secretsmanager_get_secret_value,
]
}

data "archive_file" "this" {
Expand Down Expand Up @@ -78,3 +83,72 @@ data "aws_iam_policy_document" "cloudwatch-log-group" {
resources = ["${aws_cloudwatch_log_group.this.arn}:*"]
}
}


# Secret environment variables

locals {
environment_variables = merge(local.lambda_exec_wrapper, var.environment_variables, local.secret_environment_variables)

lambda_exec_wrapper = length(var.secret_environment_variables) == 0 ? {} : {
AWS_LAMBDA_EXEC_WRAPPER = "/opt/main"
}

secret_environment_variables = {
for k, v in var.secret_environment_variables : join("", [k, "_SECRET_ARN"]) => v
}

secrets_wrapper_layers = length(var.secret_environment_variables) <= 0 ? [] : [
# sets the number suffix of lambda-layer version secrets.wrapper.lambda-layer.
# cf. https://github.com/lessonnine/secrets.wrapper.lambda-layer#readme
"arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:layer:secrets_wrapper:${var.secrets_wrapper_lambda_layer_version_number}"
]
}

data "aws_caller_identity" "current" {}

data "aws_region" "current" {}

resource "aws_iam_role_policy" "secretsmanager-get-secret-value" {
count = length(var.secret_environment_variables) > 0 ? 1 : 0

name = "secretsmanager-get-secret-value-${md5(data.aws_iam_policy_document.secretsmanager-get-secret-value[count.index].json)}"
role = aws_iam_role.this.name
policy = data.aws_iam_policy_document.secretsmanager-get-secret-value[count.index].json

lifecycle {
create_before_destroy = true
}
}

# Whenever a change is made to the secrets, the role policy must be updated
# first, then we need to wait for a few seconds and only then update the lambda function itself.
# Waiting is necessary because otherwise the during initialization of the lambda function the
# secrets-fetcher wrapper will not yet have the permissions for fetching the secret values.
resource "null_resource" "watch_iam_role_policy_secretsmanager_get_secret_value" {
count = length(var.secret_environment_variables) > 0 ? 1 : 0

# null_resource is replaced every time the policy changes
triggers = {
secretsmanager_get_secret_value_policy = aws_iam_role_policy.secretsmanager-get-secret-value[count.index].policy
}

provisioner "local-exec" {
command = "sleep 15"
}
}

data "aws_iam_policy_document" "secretsmanager-get-secret-value" {
count = length(var.secret_environment_variables) > 0 ? 1 : 0

statement {
actions = ["secretsmanager:GetSecretValue"]

# select secret arns and cut off trailing json keys
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data-secrets.html
resources = [
for k, extended_secret_arn in local.secret_environment_variables :
join(":", slice(split(":", extended_secret_arn), 0, 7)) if length(regexall("_SECRET_ARN$", k)) == 1
]
}
}
21 changes: 21 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,27 @@ variable "runtime" {
description = "The identifier of the Lambda function [runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html)."
}

variable "secret_environment_variables" {
type = map(string)
default = {}

description = <<EOS
Map of environment variables' names to ARNs of AWS Secret Manager secrets. During the lambda's initialisation ARNs will be substituted by the secret values they are referencing and passed as environment variables.

Each ARN will be passed as environment variable to the lambda function with the key's name extended by suffix _SECRET_ARN. A lambda layer will be added containing a wrapper script. When initializing the lambda run time environment the wrapper script will look up the secret value and create a new environment variable with the original name (i.e. without suffix) and set the secret's value as value.
Each ARN may also be extended by a JSON key suffix (e.g. "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c:some-json-key::") for setting the secret value from JSON object's elements.

Permission will be added allowing the wrapper script to read the secret values. Also, to enable the layer, the AWS_LAMBDA_EXEC_WRAPPER environment variable will be set.
EOS
}

variable "secrets_wrapper_lambda_layer_version_number" {
type = string
default = null

description = "Lambda layer version number of the secrts wrapper to use with secret_environment_variables. Required when used with non-empty secret_environment_variables."
}

variable "source_dir" {
type = string
default = null
Expand Down