From e0eaa9e4870a17b3f1587276cb8c4e89b4cb5c4c Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Fri, 28 Aug 2020 10:25:00 +0200 Subject: [PATCH 1/2] streamlined tests and variables - use objects in favour of maps to have a more precise API and documentation (those are interchangeable from a client perspective) - use lambda fixture in all tests - streamlined examples - added deprecation comments for ssm - use `kms_key_arn` (also) as described in https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function#kms_key_arn --- .gitignore | 1 + README.md | 7 +++-- docs/part2.md | 7 +++-- .../example-with-cloudwatch-event/main.tf | 28 ++++++++++------- examples/example-with-dynamodb-event/main.tf | 26 ++++++---------- examples/example-with-kinesis-event/main.tf | 26 ++++++---------- examples/example-with-s3-event/main.tf | 26 ++++++---------- examples/example-with-sns-event/main.tf | 26 ++++++---------- examples/example-with-sqs-event/main.tf | 26 ++++++---------- examples/example-with-vpc/main.tf | 31 ++++++------------- examples/example-without-event/main.tf | 26 ++++++++++++---- examples/fixtures/main.tf | 9 ++++++ examples/fixtures/outputs.tf | 7 +++++ main.tf | 21 ++++++++++--- modules/lambda/main.tf | 7 +++-- modules/lambda/variables.tf | 18 ++++++++--- variables.tf | 15 ++++++--- 17 files changed, 165 insertions(+), 142 deletions(-) create mode 100644 examples/fixtures/main.tf create mode 100644 examples/fixtures/outputs.tf diff --git a/.gitignore b/.gitignore index fd2b4dd..f344637 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ terraform.tfstate.backup bin/ .idea +lambda.zip diff --git a/README.md b/README.md index 585a5e1..b18260c 100644 --- a/README.md +++ b/README.md @@ -137,12 +137,12 @@ MINOR, and PATCH versions on each release to indicate any incompatibilities. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | description | Description of what your Lambda Function does. | `string` | `""` | no | -| environment | Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries | `map(map(string))` | `{}` | no | +| environment | Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries |
object({
variables = map(string)
})
| `null` | no | | event | Event source configuration which triggers the Lambda function. Supported events: cloudwatch-scheduled-event, dynamodb, s3, sns | `map(string)` | `{}` | no | | filename | The path to the function's deployment package within the local filesystem. If defined, The s3\_-prefixed options cannot be used. | `string` | `""` | no | | function\_name | A unique name for your Lambda Function. | `any` | n/a | yes | | handler | The function entrypoint in your code. | `any` | n/a | yes | -| kms\_key\_arn | The Amazon Resource Name (ARN) of the KMS key to decrypt AWS Systems Manager parameters. | `string` | `""` | no | +| kms\_key\_arn | Amazon Resource Name (ARN) of the AWS Key Management Service (KMS) key that is used to encrypt environment variables. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key. If this configuration is provided when environment variables are not in use, the AWS Lambda API does not save this configuration and Terraform will show a perpetual difference of adding the key. To fix the perpetual difference, remove this configuration. | `string` | `""` | no | | layers | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `[]` | no | | log\_retention\_in\_days | Specifies the number of days you want to retain log events in the specified log group. Defaults to 14. | `number` | `14` | no | | logfilter\_destination\_arn | The ARN of the destination to deliver matching log events to. Kinesis stream or Lambda function ARN. | `string` | `""` | no | @@ -157,7 +157,7 @@ MINOR, and PATCH versions on each release to indicate any incompatibilities. | ssm\_parameter\_names | List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms\_key\_arn needs to be provided as well. | `list` | `[]` | no | | tags | A mapping of tags to assign to the Lambda function. | `map(string)` | `{}` | no | | timeout | The amount of time your Lambda Function has to run in seconds. Defaults to 3. | `number` | `3` | no | -| vpc\_config | Provide this to allow your function to access your VPC (if both 'subnet\_ids' and 'security\_group\_ids' are empty then vpc\_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details). | `map(list(string))` | `{}` | no | +| vpc\_config | Provide this to allow your function to access your VPC (if both 'subnet\_ids' and 'security\_group\_ids' are empty then vpc\_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details). |
object({
security_group_ids = list(string)
subnet_ids = list(string)
})
| `null` | no | ## Outputs @@ -167,3 +167,4 @@ MINOR, and PATCH versions on each release to indicate any incompatibilities. | function\_name | The unique name of your Lambda Function. | | invoke\_arn | The ARN to be used for invoking Lambda Function from API Gateway - to be used in aws\_api\_gateway\_integration's uri | | role\_name | The name of the IAM role attached to the Lambda Function. | + diff --git a/docs/part2.md b/docs/part2.md index c00dbb8..1ff42dd 100644 --- a/docs/part2.md +++ b/docs/part2.md @@ -15,12 +15,12 @@ | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | description | Description of what your Lambda Function does. | `string` | `""` | no | -| environment | Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries | `map(map(string))` | `{}` | no | +| environment | Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries |
object({
variables = map(string)
})
| `null` | no | | event | Event source configuration which triggers the Lambda function. Supported events: cloudwatch-scheduled-event, dynamodb, s3, sns | `map(string)` | `{}` | no | | filename | The path to the function's deployment package within the local filesystem. If defined, The s3\_-prefixed options cannot be used. | `string` | `""` | no | | function\_name | A unique name for your Lambda Function. | `any` | n/a | yes | | handler | The function entrypoint in your code. | `any` | n/a | yes | -| kms\_key\_arn | The Amazon Resource Name (ARN) of the KMS key to decrypt AWS Systems Manager parameters. | `string` | `""` | no | +| kms\_key\_arn | Amazon Resource Name (ARN) of the AWS Key Management Service (KMS) key that is used to encrypt environment variables. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key. If this configuration is provided when environment variables are not in use, the AWS Lambda API does not save this configuration and Terraform will show a perpetual difference of adding the key. To fix the perpetual difference, remove this configuration. | `string` | `""` | no | | layers | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `[]` | no | | log\_retention\_in\_days | Specifies the number of days you want to retain log events in the specified log group. Defaults to 14. | `number` | `14` | no | | logfilter\_destination\_arn | The ARN of the destination to deliver matching log events to. Kinesis stream or Lambda function ARN. | `string` | `""` | no | @@ -35,7 +35,7 @@ | ssm\_parameter\_names | List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms\_key\_arn needs to be provided as well. | `list` | `[]` | no | | tags | A mapping of tags to assign to the Lambda function. | `map(string)` | `{}` | no | | timeout | The amount of time your Lambda Function has to run in seconds. Defaults to 3. | `number` | `3` | no | -| vpc\_config | Provide this to allow your function to access your VPC (if both 'subnet\_ids' and 'security\_group\_ids' are empty then vpc\_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details). | `map(list(string))` | `{}` | no | +| vpc\_config | Provide this to allow your function to access your VPC (if both 'subnet\_ids' and 'security\_group\_ids' are empty then vpc\_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details). |
object({
security_group_ids = list(string)
subnet_ids = list(string)
})
| `null` | no | ## Outputs @@ -45,3 +45,4 @@ | function\_name | The unique name of your Lambda Function. | | invoke\_arn | The ARN to be used for invoking Lambda Function from API Gateway - to be used in aws\_api\_gateway\_integration's uri | | role\_name | The name of the IAM role attached to the Lambda Function. | + diff --git a/examples/example-with-cloudwatch-event/main.tf b/examples/example-with-cloudwatch-event/main.tf index 3e2ae85..8f2b510 100644 --- a/examples/example-with-cloudwatch-event/main.tf +++ b/examples/example-with-cloudwatch-event/main.tf @@ -2,14 +2,18 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda-scheduled" { source = "../../" - description = "Example AWS Lambda using go with cloudwatch scheduled event trigger" - filename = "${path.module}/test_function.zip" - function_name = "tf-example-go-basic" - handler = "example-lambda-func" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a CloudWatch (scheduled) event trigger." + filename = module.source.output_path + function_name = "example-with-cloudwatch-scheduled-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "cloudwatch-event" @@ -19,12 +23,12 @@ module "lambda-scheduled" { module "lambda-pattern" { source = "../../" - description = "Example AWS Lambda using go with cloudwatch event pattern trigger" - filename = "${path.module}/test_function.zip" - function_name = "tf-example-go-basic" - handler = "example-lambda-func" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a CloudWatch event trigger." + filename = module.source.output_path + function_name = "example-with-cloudwatch-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "cloudwatch-event" diff --git a/examples/example-with-dynamodb-event/main.tf b/examples/example-with-dynamodb-event/main.tf index 0da891d..5f27a9f 100644 --- a/examples/example-with-dynamodb-event/main.tf +++ b/examples/example-with-dynamodb-event/main.tf @@ -2,27 +2,21 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda" { source = "../../" - filename = "${path.module}/test_function.zip" - function_name = "my-function" - handler = "my-handler" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a DynamoDb event trigger." + filename = module.source.output_path + function_name = "example-with-dynamodb-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "dynamodb" event_source_arn = "arn:aws:dynamodb:eu-west-1:647379381847:table/some-table/stream/some-identifier" } - - # optionally configure Parameter Store access with decryption - ssm_parameter_names = ["some/config/root/*"] - kms_key_arn = "arn:aws:kms:eu-west-1:647379381847:key/f79f2b-04684-4ad9-f9de8a-79d72f" - - # optionally create a log subscription for streaming log events - logfilter_destination_arn = "arn:aws:lambda:eu-west-1:647379381847:function:cloudwatch_logs_to_es_production" - - tags = { - key = "value" - } } diff --git a/examples/example-with-kinesis-event/main.tf b/examples/example-with-kinesis-event/main.tf index c90b447..347eb0e 100644 --- a/examples/example-with-kinesis-event/main.tf +++ b/examples/example-with-kinesis-event/main.tf @@ -2,27 +2,21 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda" { source = "../../" - filename = "${path.module}/test_function.zip" - function_name = "my-function" - handler = "my-handler" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a Kinesis event trigger." + filename = module.source.output_path + function_name = "example-with-kinesis-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "kinesis" event_source_arn = "arn:aws:kinesis:eu-west-1:647379381847:stream/my-stream" } - - # optionally configure Parameter Store access with decryption - ssm_parameter_names = ["some/config/root/*"] - kms_key_arn = "arn:aws:kms:eu-west-1:647379381847:key/f79f2b-04684-4ad9-f9de8a-79d72f" - - # optionally create a log subscription for streaming log events - logfilter_destination_arn = "arn:aws:lambda:eu-west-1:647379381847:function:cloudwatch_logs_to_es_production" - - tags = { - key = "value" - } } diff --git a/examples/example-with-s3-event/main.tf b/examples/example-with-s3-event/main.tf index f3a2d12..d17c71c 100644 --- a/examples/example-with-s3-event/main.tf +++ b/examples/example-with-s3-event/main.tf @@ -2,6 +2,10 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + resource "aws_s3_bucket_notification" "bucket_notification" { bucket = "bucketname" @@ -13,26 +17,16 @@ resource "aws_s3_bucket_notification" "bucket_notification" { module "lambda" { source = "../../" - description = "Example AWS Lambda using go with S3 trigger" - filename = "${path.module}/test_function.zip" - function_name = "tf-example-go-s3" - handler = "example-lambda-func" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a S3 bucket notification event trigger." + filename = module.source.output_path + function_name = "example-wth-s3-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "s3" s3_bucket_arn = "arn:aws:s3:::bucketname" s3_bucket_id = "bucketname" } - - tags = { - key = "value" - } - - environment = { - variables = { - key = "value" - } - } } diff --git a/examples/example-with-sns-event/main.tf b/examples/example-with-sns-event/main.tf index 974d669..1cff57f 100644 --- a/examples/example-with-sns-event/main.tf +++ b/examples/example-with-sns-event/main.tf @@ -2,27 +2,21 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda" { source = "../../" - description = "Example AWS Lambda using go with sns trigger" - filename = "${path.module}/test_function.zip" - function_name = "tf-example-go-sns" - handler = "example-lambda-func" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a SNS event trigger." + filename = module.source.output_path + function_name = "example-with-sns-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "sns" topic_arn = "arn:aws:sns:eu-west-1:123456789123:test-topic" } - - tags = { - key = "value" - } - - environment = { - variables = { - key = "value" - } - } } diff --git a/examples/example-with-sqs-event/main.tf b/examples/example-with-sqs-event/main.tf index 88fa662..f2b45f3 100644 --- a/examples/example-with-sqs-event/main.tf +++ b/examples/example-with-sqs-event/main.tf @@ -2,27 +2,21 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda" { source = "../../" - filename = "${path.module}/test_function.zip" - function_name = "my-function" - handler = "my-handler" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda with a SQS event trigger." + filename = module.source.output_path + function_name = "example-with-sqs-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 event = { type = "sqs" event_source_arn = "arn:aws:kinesis:eu-west-1:647379381847:queue-name" } - - # optionally configure Parameter Store access with decryption - ssm_parameter_names = ["some/config/root/*"] - kms_key_arn = "arn:aws:kms:eu-west-1:647379381847:key/f79f2b-04684-4ad9-f9de8a-79d72f" - - # optionally create a log subscription for streaming log events - logfilter_destination_arn = "arn:aws:lambda:eu-west-1:647379381847:function:cloudwatch_logs_to_es_production" - - tags = { - key = "value" - } } diff --git a/examples/example-with-vpc/main.tf b/examples/example-with-vpc/main.tf index 80ecd84..9635046 100644 --- a/examples/example-with-vpc/main.tf +++ b/examples/example-with-vpc/main.tf @@ -2,32 +2,21 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda" { source = "../../" - description = "Example AWS Lambda inside a VPC using go with cloudwatch scheduled event trigger" - filename = "${path.module}/test_function.zip" - function_name = "tf-example-go-basic-vpc" - handler = "example-lambda-func" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda inside a VPC." + filename = module.source.output_path + function_name = "example-with-vpc" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 vpc_config = { subnet_ids = ["subnet-123456", "subnet-123457"] security_group_ids = ["sg-123456"] } - - event = { - type = "cloudwatch-scheduled-event" - schedule_expression = "rate(1 minute)" - } - - tags = { - key = "value" - } - - environment = { - variables = { - key = "value" - } - } } diff --git a/examples/example-without-event/main.tf b/examples/example-without-event/main.tf index 6a47470..c95f05c 100644 --- a/examples/example-without-event/main.tf +++ b/examples/example-without-event/main.tf @@ -2,12 +2,26 @@ provider "aws" { region = "eu-west-1" } +module "source" { + source = "../fixtures" +} + module "lambda" { source = "../../" - description = "Example AWS Lambda using go with cloudwatch scheduled event trigger" - filename = "${path.module}/test_function.zip" - function_name = "tf-example-go-basic" - handler = "example-lambda-func" - runtime = "go1.x" - source_code_hash = filebase64sha256("${path.module}/test_function.zip") + description = "Example usage for an AWS Lambda without an event trigger." + filename = module.source.output_path + function_name = "example-without-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 + + environment = { + variables = { + key = "value" + } + } + + tags = { + key = "value" + } } diff --git a/examples/fixtures/main.tf b/examples/fixtures/main.tf new file mode 100644 index 0000000..ce573e6 --- /dev/null +++ b/examples/fixtures/main.tf @@ -0,0 +1,9 @@ +data "archive_file" "lambda" { + output_path = "${path.module}/lambda.zip" + type = "zip" + + source { + content = "exports.handler = async function(event, context) { \n return context.logStreamName" + filename = "index.js" + } +} diff --git a/examples/fixtures/outputs.tf b/examples/fixtures/outputs.tf new file mode 100644 index 0000000..3c1b7ba --- /dev/null +++ b/examples/fixtures/outputs.tf @@ -0,0 +1,7 @@ +output "output_path" { + value = data.archive_file.lambda.output_path +} + +output "output_base64sha256" { + value = data.archive_file.lambda.output_base64sha256 +} \ No newline at end of file diff --git a/main.tf b/main.tf index 1723a5e..bd87c5e 100644 --- a/main.tf +++ b/main.tf @@ -11,8 +11,9 @@ module "lambda" { filename = var.filename function_name = var.function_name handler = var.handler - memory_size = var.memory_size + kms_key_arn = var.kms_key_arn layers = var.layers + memory_size = var.memory_size publish = var.publish reserved_concurrent_executions = var.reserved_concurrent_executions runtime = var.runtime @@ -114,6 +115,7 @@ resource "aws_cloudwatch_log_subscription_filter" "cloudwatch_logs_to_es" { distribution = "ByLogStream" } +// Deprecated - will be removed in the next major version data "aws_iam_policy_document" "ssm_policy_document" { count = length(var.ssm_parameter_names) @@ -129,6 +131,7 @@ data "aws_iam_policy_document" "ssm_policy_document" { } } +// Deprecated - will be removed in the next major version resource "aws_iam_policy" "ssm_policy" { count = length(var.ssm_parameter_names) name = "${module.lambda.function_name}-ssm-${count.index}-${data.aws_region.current.name}" @@ -136,13 +139,17 @@ resource "aws_iam_policy" "ssm_policy" { policy = data.aws_iam_policy_document.ssm_policy_document[count.index].json } +// Deprecated - will be removed in the next major version resource "aws_iam_role_policy_attachment" "ssm_policy_attachment" { count = length(var.ssm_parameter_names) role = module.lambda.role_name policy_arn = aws_iam_policy.ssm_policy[count.index].arn } +// Deprecated - will be removed in the next major version data "aws_iam_policy_document" "kms_policy_document" { + count = var.kms_key_arn != "" ? 1 : 0 + statement { actions = [ "kms:Decrypt", @@ -154,15 +161,19 @@ data "aws_iam_policy_document" "kms_policy_document" { } } +// Deprecated - will be removed in the next major version resource "aws_iam_policy" "kms_policy" { - count = var.kms_key_arn != "" ? 1 : 0 + count = var.kms_key_arn != "" ? 1 : 0 + name = "${module.lambda.function_name}-kms-${data.aws_region.current.name}" description = "Provides minimum KMS permissions for ${module.lambda.function_name}." - policy = data.aws_iam_policy_document.kms_policy_document.json + policy = data.aws_iam_policy_document.kms_policy_document[count.index].json } +// Deprecated - will be removed in the next major version resource "aws_iam_role_policy_attachment" "kms_policy_attachment" { - count = var.kms_key_arn != "" ? 1 : 0 + count = var.kms_key_arn != "" ? 1 : 0 + role = module.lambda.role_name policy_arn = aws_iam_policy.kms_policy[count.index].arn -} +} \ No newline at end of file diff --git a/modules/lambda/main.tf b/modules/lambda/main.tf index 8956dc4..8ff874c 100644 --- a/modules/lambda/main.tf +++ b/modules/lambda/main.tf @@ -4,7 +4,7 @@ data "aws_region" "current" { resource "aws_lambda_function" "lambda" { description = var.description dynamic "environment" { - for_each = length(var.environment) < 1 ? [] : [var.environment] + for_each = var.environment == null ? [] : [var.environment] content { variables = environment.value.variables } @@ -13,6 +13,7 @@ resource "aws_lambda_function" "lambda" { function_name = var.function_name handler = var.handler layers = var.layers + kms_key_arn = var.kms_key_arn memory_size = var.memory_size publish = var.publish reserved_concurrent_executions = var.reserved_concurrent_executions @@ -26,7 +27,7 @@ resource "aws_lambda_function" "lambda" { timeout = var.timeout dynamic "vpc_config" { - for_each = length(var.vpc_config) < 1 ? [] : [var.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 @@ -56,7 +57,7 @@ resource "aws_iam_role_policy_attachment" "cloudwatch_logs" { } resource "aws_iam_role_policy_attachment" "vpc_attachment" { - count = length(var.vpc_config) < 1 ? 0 : 1 + count = var.vpc_config == null ? 0 : 1 role = aws_iam_role.lambda.name // see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html diff --git a/modules/lambda/variables.tf b/modules/lambda/variables.tf index 6f6d4b0..4c8a4c7 100644 --- a/modules/lambda/variables.tf +++ b/modules/lambda/variables.tf @@ -31,8 +31,15 @@ variable "description" { variable "environment" { description = "Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries" - type = map(map(string)) - default = {} + type = object({ + variables = map(string) + }) + default = null +} + +variable "kms_key_arn" { + description = "Amazon Resource Name (ARN) of the AWS Key Management Service (KMS) key that is used to encrypt environment variables. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key. If this configuration is provided when environment variables are not in use, the AWS Lambda API does not save this configuration and Terraform will show a perpetual difference of adding the key. To fix the perpetual difference, remove this configuration." + default = "" } variable "layers" { @@ -88,7 +95,10 @@ variable "timeout" { } variable "vpc_config" { + default = null description = "Provide this to allow your function to access your VPC (if both 'subnet_ids' and 'security_group_ids' are empty then vpc_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details)." - type = map(list(string)) - default = {} + type = object({ + security_group_ids = list(string) + subnet_ids = list(string) + }) } diff --git a/variables.tf b/variables.tf index d392c4a..05c336d 100644 --- a/variables.tf +++ b/variables.tf @@ -28,8 +28,10 @@ variable "description" { variable "environment" { description = "Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries" - type = map(map(string)) - default = {} + type = object({ + variables = map(string) + }) + default = null } variable "event" { @@ -44,7 +46,7 @@ variable "filename" { } variable "kms_key_arn" { - description = "The Amazon Resource Name (ARN) of the KMS key to decrypt AWS Systems Manager parameters." + description = "Amazon Resource Name (ARN) of the AWS Key Management Service (KMS) key that is used to encrypt environment variables. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key. If this configuration is provided when environment variables are not in use, the AWS Lambda API does not save this configuration and Terraform will show a perpetual difference of adding the key. To fix the perpetual difference, remove this configuration." default = "" } @@ -116,7 +118,10 @@ variable "timeout" { } variable "vpc_config" { + default = null description = "Provide this to allow your function to access your VPC (if both 'subnet_ids' and 'security_group_ids' are empty then vpc_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details)." - type = map(list(string)) - default = {} + type = object({ + security_group_ids = list(string) + subnet_ids = list(string) + }) } From c7cc9710539fb617632cf48dfd19eb1afd1484e4 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Fri, 28 Aug 2020 11:26:32 +0200 Subject: [PATCH 2/2] new configuration for ssm - custom object which can be enhanced for kms_key later - adapted documentation and added example - allow `ssm:GetParameter` for spring-media users, this fixes https://github.com/spring-media/terraform-aws-lambda/issues/61 and https://github.com/spring-media/terraform-aws-lambda/issues/59 --- README.md | 13 ++++--- docs/part1.md | 10 +++-- docs/part2.md | 3 +- .../example-with-ssm-permissions/README.md | 15 ++++++++ examples/example-with-ssm-permissions/main.tf | 33 ++++++++++++++++ .../example-with-ssm-permissions/versions.tf | 4 ++ examples/example-with-vpc/README.md | 2 +- main.tf | 38 +++++++++++++++++-- variables.tf | 20 +++++++--- 9 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 examples/example-with-ssm-permissions/README.md create mode 100644 examples/example-with-ssm-permissions/main.tf create mode 100644 examples/example-with-ssm-permissions/versions.tf diff --git a/README.md b/README.md index b18260c..ea0b874 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![](https://github.com/moritzzimmer/terraform-aws-lambda/workflows/Terraform%20CI/badge.svg) [![Terraform Module Registry](https://img.shields.io/badge/Terraform%20Module%20Registry-5.3.0-blue.svg)](https://registry.terraform.io/modules/moritzzimmer/lambda/aws/5.3.0) ![Terraform Version](https://img.shields.io/badge/Terraform-0.12+-green.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -Terraform module to create AWS [Lambda](https://www.terraform.io/docs/providers/aws/r/lambda_function.html) resources with configurable event sources, IAM configuration (following the [principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)), VPC as well as SSM/KMS and log streaming support. +Terraform module to create AWS [Lambda](https://www.terraform.io/docs/providers/aws/r/lambda_function.html) resources with configurable event sources, IAM configuration (following the [principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)), VPC as well as SSM and log streaming support. The following [event sources](https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html) are supported (see [examples](#examples)): @@ -15,7 +15,7 @@ The following [event sources](https://docs.aws.amazon.com/lambda/latest/dg/invok Furthermore this module supports: -- reading configuration and secrets from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html) including decryption of [SecureString](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) parameters +- adding IAM permissions for read access to parameters from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html) - [CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html) Log group configuration including retention time and [subscription filters](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html) e.g. to stream logs via Lambda to Elasticsearch ## History @@ -79,8 +79,9 @@ module "lambda" { module "lambda" { // see above - ssm_parameter_names = ["some/config/root/*"] - kms_key_arn = "arn:aws:kms:eu-west-1:647379381847:key/f79f2b-04684-4ad9-f9de8a-79d72f" + ssm = { + parameter_names = [aws_ssm_parameter.string.name, aws_ssm_parameter.secure_string.name] + } } ``` @@ -102,6 +103,7 @@ module "lambda" { - [example-with-s3-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-s3-event) - [example-with-sns-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-sns-event) - [example-with-sqs-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-sqs-event) +- [example-with-ssm-permissions](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-ssm-permissions) - [example-with-vpc](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-vpc) - [example-without-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-without-event) @@ -154,7 +156,8 @@ MINOR, and PATCH versions on each release to indicate any incompatibilities. | s3\_key | The S3 key of an object containing the function's deployment package. Conflicts with filename. | `string` | `""` | no | | s3\_object\_version | The object version containing the function's deployment package. Conflicts with filename. | `string` | `""` | no | | source\_code\_hash | Used to trigger updates. Must be set to a base64-encoded SHA256 hash of the package file specified with either filename or s3\_key. The usual way to set this is filebase64sha256('file.zip') where 'file.zip' is the local filename of the lambda function source archive. | `string` | `""` | no | -| ssm\_parameter\_names | List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms\_key\_arn needs to be provided as well. | `list` | `[]` | no | +| ssm | List of AWS Systems Manager Parameter Store parameter names. The IAM role of this Lambda function will be enhanced with read permissions for those parameters. Parameters must start with a forward slash and can be encrypted with the default KMS key. |
object({
parameter_names = list(string)
})
| `null` | no | +| ssm\_parameter\_names | DEPRECATED: use `ssm` object instead. This variable will be removed in version 6 of this module. (List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms\_key\_arn needs to be provided as well.) | `list` | `[]` | no | | tags | A mapping of tags to assign to the Lambda function. | `map(string)` | `{}` | no | | timeout | The amount of time your Lambda Function has to run in seconds. Defaults to 3. | `number` | `3` | no | | vpc\_config | Provide this to allow your function to access your VPC (if both 'subnet\_ids' and 'security\_group\_ids' are empty then vpc\_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details). |
object({
security_group_ids = list(string)
subnet_ids = list(string)
})
| `null` | no | diff --git a/docs/part1.md b/docs/part1.md index 7a5f45c..7b34d8f 100644 --- a/docs/part1.md +++ b/docs/part1.md @@ -2,7 +2,7 @@ ![](https://github.com/moritzzimmer/terraform-aws-lambda/workflows/Terraform%20CI/badge.svg) [![Terraform Module Registry](https://img.shields.io/badge/Terraform%20Module%20Registry-5.3.0-blue.svg)](https://registry.terraform.io/modules/moritzzimmer/lambda/aws/5.3.0) ![Terraform Version](https://img.shields.io/badge/Terraform-0.12+-green.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -Terraform module to create AWS [Lambda](https://www.terraform.io/docs/providers/aws/r/lambda_function.html) resources with configurable event sources, IAM configuration (following the [principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)), VPC as well as SSM/KMS and log streaming support. +Terraform module to create AWS [Lambda](https://www.terraform.io/docs/providers/aws/r/lambda_function.html) resources with configurable event sources, IAM configuration (following the [principal of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)), VPC as well as SSM and log streaming support. The following [event sources](https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html) are supported (see [examples](#examples)): @@ -15,7 +15,7 @@ The following [event sources](https://docs.aws.amazon.com/lambda/latest/dg/invok Furthermore this module supports: -- reading configuration and secrets from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html) including decryption of [SecureString](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) parameters +- adding IAM permissions for read access to parameters from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html) - [CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html) Log group configuration including retention time and [subscription filters](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html) e.g. to stream logs via Lambda to Elasticsearch ## History @@ -79,8 +79,9 @@ module "lambda" { module "lambda" { // see above - ssm_parameter_names = ["some/config/root/*"] - kms_key_arn = "arn:aws:kms:eu-west-1:647379381847:key/f79f2b-04684-4ad9-f9de8a-79d72f" + ssm = { + parameter_names = [aws_ssm_parameter.string.name, aws_ssm_parameter.secure_string.name] + } } ``` @@ -102,6 +103,7 @@ module "lambda" { - [example-with-s3-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-s3-event) - [example-with-sns-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-sns-event) - [example-with-sqs-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-sqs-event) +- [example-with-ssm-permissions](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-ssm-permissions) - [example-with-vpc](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-with-vpc) - [example-without-event](https://github.com/moritzzimmer/terraform-aws-lambda/tree/master/examples/example-without-event) diff --git a/docs/part2.md b/docs/part2.md index 1ff42dd..9320740 100644 --- a/docs/part2.md +++ b/docs/part2.md @@ -32,7 +32,8 @@ | s3\_key | The S3 key of an object containing the function's deployment package. Conflicts with filename. | `string` | `""` | no | | s3\_object\_version | The object version containing the function's deployment package. Conflicts with filename. | `string` | `""` | no | | source\_code\_hash | Used to trigger updates. Must be set to a base64-encoded SHA256 hash of the package file specified with either filename or s3\_key. The usual way to set this is filebase64sha256('file.zip') where 'file.zip' is the local filename of the lambda function source archive. | `string` | `""` | no | -| ssm\_parameter\_names | List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms\_key\_arn needs to be provided as well. | `list` | `[]` | no | +| ssm | List of AWS Systems Manager Parameter Store parameter names. The IAM role of this Lambda function will be enhanced with read permissions for those parameters. Parameters must start with a forward slash and can be encrypted with the default KMS key. |
object({
parameter_names = list(string)
})
| `null` | no | +| ssm\_parameter\_names | DEPRECATED: use `ssm` object instead. This variable will be removed in version 6 of this module. (List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms\_key\_arn needs to be provided as well.) | `list` | `[]` | no | | tags | A mapping of tags to assign to the Lambda function. | `map(string)` | `{}` | no | | timeout | The amount of time your Lambda Function has to run in seconds. Defaults to 3. | `number` | `3` | no | | vpc\_config | Provide this to allow your function to access your VPC (if both 'subnet\_ids' and 'security\_group\_ids' are empty then vpc\_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details). |
object({
security_group_ids = list(string)
subnet_ids = list(string)
})
| `null` | no | diff --git a/examples/example-with-ssm-permissions/README.md b/examples/example-with-ssm-permissions/README.md new file mode 100644 index 0000000..fc39e3f --- /dev/null +++ b/examples/example-with-ssm-permissions/README.md @@ -0,0 +1,15 @@ +# Example without event + +Creates an AWS Lambda function with read permissions to SSM parameters. + +## requirements + +- [Terraform 0.12+](https://www.terraform.io/) +- authentication configuration for the [aws provider](https://www.terraform.io/docs/providers/aws/) + +## usage + +``` +terraform init +terraform plan +``` diff --git a/examples/example-with-ssm-permissions/main.tf b/examples/example-with-ssm-permissions/main.tf new file mode 100644 index 0000000..c6c8aa3 --- /dev/null +++ b/examples/example-with-ssm-permissions/main.tf @@ -0,0 +1,33 @@ +provider "aws" { + region = "eu-west-1" +} + +module "source" { + source = "../fixtures" +} + +resource "aws_ssm_parameter" "string" { + name = "/example/string" + type = "String" + value = "changeme" +} + +resource "aws_ssm_parameter" "secure_string" { + name = "/example/secure.string" + type = "SecureString" + value = "changeme" +} + +module "lambda" { + source = "../../" + description = "Example usage for an AWS Lambda with read permissions to SSM parameters." + filename = module.source.output_path + function_name = "example-without-event" + handler = "handler" + runtime = "nodejs12.x" + source_code_hash = module.source.output_base64sha256 + + ssm = { + parameter_names = [aws_ssm_parameter.string.name, aws_ssm_parameter.secure_string.name] + } +} diff --git a/examples/example-with-ssm-permissions/versions.tf b/examples/example-with-ssm-permissions/versions.tf new file mode 100644 index 0000000..ac97c6a --- /dev/null +++ b/examples/example-with-ssm-permissions/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/examples/example-with-vpc/README.md b/examples/example-with-vpc/README.md index 4a85cd9..8bc5eeb 100644 --- a/examples/example-with-vpc/README.md +++ b/examples/example-with-vpc/README.md @@ -1,6 +1,6 @@ # Example with VPC and CloudWatch scheduled event -Creates an AWS Lambda function inside a VPC triggered by a CloudWatch (scheduled) [event](https://docs.aws.amazon.com/lambda/latest/dg/with-scheduled-events.html). +Creates an AWS Lambda function inside a VPC. ## requirements diff --git a/main.tf b/main.tf index bd87c5e..8ca90b5 100644 --- a/main.tf +++ b/main.tf @@ -115,6 +115,36 @@ resource "aws_cloudwatch_log_subscription_filter" "cloudwatch_logs_to_es" { distribution = "ByLogStream" } +data "aws_iam_policy_document" "ssm" { + count = try((var.ssm != null && length(var.ssm.parameter_names) > 0), false) ? 1 : 0 + + statement { + actions = [ + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:GetParametersByPath", + ] + + resources = formatlist("arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter%s", var.ssm.parameter_names) + } +} + +resource "aws_iam_policy" "ssm" { + count = try((var.ssm != null && length(var.ssm.parameter_names) > 0), false) ? 1 : 0 + + description = "Provides minimum SSM read permissions." + name = "${var.function_name}-ssm-policy-${data.aws_region.current.name}" + policy = data.aws_iam_policy_document.ssm[count.index].json +} + +resource "aws_iam_role_policy_attachment" "ssm" { + count = try((var.ssm != null && length(var.ssm.parameter_names) > 0), false) ? 1 : 0 + + role = module.lambda.role_name + policy_arn = aws_iam_policy.ssm[count.index].arn +} + + // Deprecated - will be removed in the next major version data "aws_iam_policy_document" "ssm_policy_document" { count = length(var.ssm_parameter_names) @@ -134,8 +164,8 @@ data "aws_iam_policy_document" "ssm_policy_document" { // Deprecated - will be removed in the next major version resource "aws_iam_policy" "ssm_policy" { count = length(var.ssm_parameter_names) - name = "${module.lambda.function_name}-ssm-${count.index}-${data.aws_region.current.name}" - description = "Provides minimum Parameter Store permissions for ${module.lambda.function_name}." + name = "${var.function_name}-ssm-${count.index}-${data.aws_region.current.name}" + description = "Provides minimum Parameter Store permissions for ${var.function_name}." policy = data.aws_iam_policy_document.ssm_policy_document[count.index].json } @@ -165,8 +195,8 @@ data "aws_iam_policy_document" "kms_policy_document" { resource "aws_iam_policy" "kms_policy" { count = var.kms_key_arn != "" ? 1 : 0 - name = "${module.lambda.function_name}-kms-${data.aws_region.current.name}" - description = "Provides minimum KMS permissions for ${module.lambda.function_name}." + name = "${var.function_name}-kms-${data.aws_region.current.name}" + description = "Provides minimum KMS permissions for ${var.function_name}." policy = data.aws_iam_policy_document.kms_policy_document[count.index].json } diff --git a/variables.tf b/variables.tf index 05c336d..99d0b2f 100644 --- a/variables.tf +++ b/variables.tf @@ -28,16 +28,16 @@ variable "description" { variable "environment" { description = "Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries" + default = null type = object({ variables = map(string) }) - default = null } variable "event" { description = "Event source configuration which triggers the Lambda function. Supported events: cloudwatch-scheduled-event, dynamodb, s3, sns" - type = map(string) default = {} + type = map(string) } variable "filename" { @@ -51,8 +51,8 @@ variable "kms_key_arn" { } variable "layers" { - default = [] description = "List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function." + default = [] type = list(string) } @@ -101,15 +101,23 @@ variable "source_code_hash" { default = "" } +variable "ssm" { + description = "List of AWS Systems Manager Parameter Store parameter names. The IAM role of this Lambda function will be enhanced with read permissions for those parameters. Parameters must start with a forward slash and can be encrypted with the default KMS key." + default = null + type = object({ + parameter_names = list(string) + }) +} + variable "ssm_parameter_names" { - description = "List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms_key_arn needs to be provided as well." + description = "DEPRECATED: use `ssm` object instead. This variable will be removed in version 6 of this module. (List of AWS Systems Manager Parameter Store parameters this Lambda will have access to. In order to decrypt secure parameters, a kms_key_arn needs to be provided as well.)" default = [] } variable "tags" { description = "A mapping of tags to assign to the Lambda function." - type = map(string) default = {} + type = map(string) } variable "timeout" { @@ -118,8 +126,8 @@ variable "timeout" { } variable "vpc_config" { - default = null description = "Provide this to allow your function to access your VPC (if both 'subnet_ids' and 'security_group_ids' are empty then vpc_config is considered to be empty or unset, see https://docs.aws.amazon.com/lambda/latest/dg/vpc.html for details)." + default = null type = object({ security_group_ids = list(string) subnet_ids = list(string)