Skip to content

Commit

Permalink
feat: allow KMS encryption of token environment variable
Browse files Browse the repository at this point in the history
This commit adds support for encrypting the `OBSERVE_TOKEN` environment
variable in transit.

Previously, this module accepted a `kms_key_arn` variable which affected
all environment variables _at rest_. However, this still exposed the
token in different contexts (e.g. AWS Config). We now allow reusing the
KMS key to encrypt the variable, which gets decrypted by our lambda as
of version `v1.0.20240501`.

This commit also introduces a subtle API change to the module. We pass
in an object, `kms_key`, rather than a string, `kms_key_arn`. This is
more friendly to the `count` operator, which cannot determine the value
of an attribute until apply time.
  • Loading branch information
jta committed May 1, 2024
1 parent f55e2d8 commit 3eec858
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 10 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ No modules.
| Name | Type |
|------|------|
| [aws_cloudwatch_log_group.group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
| [aws_iam_policy.kms_decrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy.lambda_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy.vpc_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.kms_decrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.lambda_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.vpc_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_kms_ciphertext.token](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_ciphertext) | resource |
| [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

Expand All @@ -69,7 +72,8 @@ No modules.
| <a name="input_dead_letter_queue_destination"></a> [dead\_letter\_queue\_destination](#input\_dead\_letter\_queue\_destination) | Send failed events/function executions to a dead letter queue arn sns or sqs | `string` | `null` | no |
| <a name="input_description"></a> [description](#input\_description) | Lambda description | `string` | `"Lambda function to forward events towards Observe"` | no |
| <a name="input_iam_name_prefix"></a> [iam\_name\_prefix](#input\_iam\_name\_prefix) | Prefix used for all created IAM roles and policies | `string` | `"observe-lambda-"` | no |
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the AWS Key Management Service (AWS KMS) key that's used to encrypt your function's environment variables.<br>If it's not provided, AWS Lambda uses a default service key. | `string` | `""` | no |
| <a name="input_kms_key"></a> [kms\_key](#input\_kms\_key) | The AWS Key Management Service (AWS KMS) key that's used to encrypt your<br>function's environment variables at rest. Additionally, the Observe Token<br>will be encrypted in transit. | `object({ arn = string })` | `null` | no |
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the AWS Key Management Service (AWS KMS) key that's used to encrypt your function's environment variables.<br>If it's not provided, AWS Lambda uses a default service key. Deprecated, please use kms\_key instead" | `string` | `""` | no |
| <a name="input_lambda_envvars"></a> [lambda\_envvars](#input\_lambda\_envvars) | Environment variables | `map(any)` | `{}` | no |
| <a name="input_lambda_iam_role_arn"></a> [lambda\_iam\_role\_arn](#input\_lambda\_iam\_role\_arn) | ARN of IAM role to use for Lambda | `string` | `""` | no |
| <a name="input_lambda_s3_custom_rules"></a> [lambda\_s3\_custom\_rules](#input\_lambda\_s3\_custom\_rules) | List of rules to evaluate how to upload a given S3 object to Observe | <pre>list(object({<br> pattern = string<br> headers = map(string)<br> }))</pre> | `[]` | no |
Expand Down
49 changes: 46 additions & 3 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ locals {
lambda_iam_role_name = regex(".*role/(?P<role_name>.*)$", local.lambda_iam_role_arn)["role_name"]
s3_bucket = var.s3_bucket != "" ? var.s3_bucket : lookup(var.s3_regional_buckets, data.aws_region.current.name, local.default_lambda_bucket)
s3_key = var.s3_key != "" ? var.s3_key : join("/", [var.s3_key_prefix, format("%s.zip", var.lambda_version)])

observe_token = var.kms_key != null ? aws_kms_ciphertext.token[0].ciphertext_blob : var.observe_token
goarch = lookup(
{
"amd64" : {
Expand All @@ -29,6 +29,15 @@ locals {

data "aws_region" "current" {}

resource "aws_kms_ciphertext" "token" {
count = var.kms_key != null ? 1 : 0
key_id = var.kms_key.arn
plaintext = var.observe_token
context = {
LambdaFunctionName = var.name
}
}

resource "aws_lambda_function" "this" {
function_name = var.name
s3_bucket = local.s3_bucket
Expand All @@ -40,7 +49,7 @@ resource "aws_lambda_function" "this" {
handler = local.goarch.handler
runtime = local.goarch.runtime
description = var.description
kms_key_arn = var.kms_key_arn
kms_key_arn = var.kms_key != null ? var.kms_key.arn : var.kms_key_arn
tags = var.tags

memory_size = var.memory_size
Expand All @@ -55,7 +64,7 @@ resource "aws_lambda_function" "this" {
environment {
variables = merge({
OBSERVE_COLLECTION_ENDPOINT = var.observe_collection_endpoint != null ? var.observe_collection_endpoint : format("https://%s.collect.%s", var.observe_customer, var.observe_domain)
OBSERVE_TOKEN = var.observe_token
OBSERVE_TOKEN = local.observe_token
}, length(var.lambda_s3_custom_rules) > 0 ? {
S3_CUSTOM_RULES = base64encode(jsonencode(var.lambda_s3_custom_rules))
} : {}
Expand Down Expand Up @@ -163,3 +172,37 @@ resource "aws_iam_role_policy_attachment" "vpc_access" {
role = local.lambda_iam_role_name
policy_arn = aws_iam_policy.vpc_access[0].arn
}

resource "aws_iam_policy" "kms_decrypt" {
count = var.kms_key != null ? 1 : 0
name_prefix = var.iam_name_prefix
description = "IAM policy for decrypting ciphertext using KMS"

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"${var.kms_key.arn}"
],
"Condition": {
"StringEquals": {
"kms:EncryptionContext:LambdaFunctionName": "${var.name}"
}
}
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "kms_decrypt" {
count = length(aws_iam_policy.kms_decrypt)
role = local.lambda_iam_role_name
policy_arn = aws_iam_policy.kms_decrypt[count.index].arn
}
17 changes: 11 additions & 6 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ variable "observe_customer" {
variable "observe_token" {
description = "Observe Token"
type = string

validation {
condition = contains(split("", var.observe_token), ":")
error_message = "Token format does not follow {datastream_id}:{datastream_secret} format."
}
}

variable "observe_domain" {
Expand Down Expand Up @@ -136,10 +131,20 @@ variable "iam_name_prefix" {
default = "observe-lambda-"
}

variable "kms_key" {
description = <<-EOF
The AWS Key Management Service (AWS KMS) key that's used to encrypt your
function's environment variables at rest. Additionally, the Observe Token
will be encrypted in transit.
EOF
type = object({ arn = string })
default = null
}

variable "kms_key_arn" {
description = <<-EOF
The ARN of the AWS Key Management Service (AWS KMS) key that's used to encrypt your function's environment variables.
If it's not provided, AWS Lambda uses a default service key.
If it's not provided, AWS Lambda uses a default service key. Deprecated, please use kms_key instead"
EOF
type = string
nullable = false
Expand Down

0 comments on commit 3eec858

Please sign in to comment.