From ecd79374a9711f3691902a6158f10204d21fe8b1 Mon Sep 17 00:00:00 2001 From: Allan Denot Date: Tue, 11 Jun 2024 18:59:25 +1000 Subject: [PATCH] Importing existing SNS + Lambda (#2) * Importing existing SNS * Updating to use lambda --- .gitignore | 1 + health-lambda.py | 32 +++++++++++++++++++++++ kms.tf | 16 ++++++------ lambda.tf | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ main.tf | 18 ++++--------- outputs.tf | 4 +-- variables.tf | 16 ++++++++++-- versions.tf | 4 +-- 8 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 .gitignore create mode 100644 health-lambda.py create mode 100644 lambda.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f71bb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +health-lambda-function-payload.zip \ No newline at end of file diff --git a/health-lambda.py b/health-lambda.py new file mode 100644 index 0000000..549e42c --- /dev/null +++ b/health-lambda.py @@ -0,0 +1,32 @@ +import boto3 +import json +import os + +def lambda_handler(event, context): + sns_client = boto3.client('sns') + topic_arn = os.environ.get('SNS_TOPIC_ARN') + event_rule_name = os.environ.get('EVENT_RULE_NAME') + + event_subject = f'ALARM: {event_rule_name}: {event["detail"]["eventTypeCode"]}' + event_message = "".join( + [ + f'{event["detail"]["service"]}\n', + f'Event Type: {event["detail"]["eventTypeCode"]}\n', + f'Status: {event["detail"]["statusCode"]}\n', + event["detail"]["eventDescription"][0]["latestDescription"], + ] + ) + + response = sns_client.publish( + TopicArn=topic_arn, + Message=event_message, + Subject=event_subject, + MessageAttributes={ + 'string': { + 'DataType': 'String', + 'StringValue': 'String' + } + } + ) + + print(response) diff --git a/kms.tf b/kms.tf index a518503..c15ea38 100644 --- a/kms.tf +++ b/kms.tf @@ -1,6 +1,6 @@ # Create the KMS key resource "aws_kms_key" "sns" { - count = var.sns_kms_encryption ? 1 : 0 + count = var.sns_kms_encryption && var.sns_topic_name != "" ? 1 : 0 deletion_window_in_days = 7 description = "SNS CMK Encryption Key" enable_key_rotation = true @@ -8,7 +8,7 @@ resource "aws_kms_key" "sns" { # Define the KMS policy document data "aws_iam_policy_document" "kms_policy_sns" { - count = var.sns_kms_encryption ? 1 : 0 + count = var.sns_kms_encryption && var.sns_topic_name != "" ? 1 : 0 statement { sid = "Enable Specific IAM User Permissions" @@ -17,7 +17,7 @@ data "aws_iam_policy_document" "kms_policy_sns" { type = "AWS" identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = [ + actions = [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", @@ -28,8 +28,8 @@ data "aws_iam_policy_document" "kms_policy_sns" { } statement { - sid = "Allow Use of Key for Specific Services" - actions = ["kms:Decrypt", "kms:GenerateDataKey*"] + sid = "Allow Use of Key for Specific Services" + actions = ["kms:Decrypt", "kms:GenerateDataKey*"] principals { type = "Service" identifiers = ["cloudwatch.amazonaws.com", "lambda.amazonaws.com"] @@ -40,7 +40,7 @@ data "aws_iam_policy_document" "kms_policy_sns" { # Update the policy of the KMS key resource "aws_kms_key_policy" "sns_policy" { - count = var.sns_kms_encryption ? 1 : 0 - key_id = aws_kms_key.sns[0].key_id - policy = data.aws_iam_policy_document.kms_policy_sns[0].json + count = var.sns_kms_encryption && var.sns_topic_name != "" ? 1 : 0 + key_id = aws_kms_key.sns[0].key_id + policy = data.aws_iam_policy_document.kms_policy_sns[0].json } diff --git a/lambda.tf b/lambda.tf new file mode 100644 index 0000000..8fde1e1 --- /dev/null +++ b/lambda.tf @@ -0,0 +1,67 @@ +resource "aws_iam_role" "health_lambda_iam" { + name = var.event_rule_name + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "" + Principal = { Service = "lambda.amazonaws.com" } + }, + ] + }) + inline_policy { + name = "sns" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["sns:Publish"] + Effect = "Allow" + Resource = var.sns_topic_name != "" ? aws_sns_topic.health_event_topic[0].arn : var.sns_topic_arn + }, + { + Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"] + Effect = "Allow" + Resource = "arn:aws:logs:*:*:*" + } + ] + }) + } +} + +data "archive_file" "health_lambda" { + type = "zip" + source_file = "${path.module}/health-lambda.py" + output_path = "${path.module}/health-lambda-function-payload.zip" +} + +resource "aws_lambda_function" "health_lambda" { + filename = "${path.module}/health-lambda-function-payload.zip" + function_name = var.event_rule_name + role = aws_iam_role.health_lambda_iam.arn + handler = "health-lambda.lambda_handler" + source_code_hash = data.archive_file.health_lambda.output_base64sha256 + runtime = "python3.12" + environment { + variables = { + SNS_TOPIC_ARN = var.sns_topic_name != "" ? aws_sns_topic.health_event_topic[0].arn : var.sns_topic_arn + EVENT_RULE_NAME = var.event_rule_name + } + } +} + +resource "aws_cloudwatch_event_target" "health_lambda" { + rule = aws_cloudwatch_event_rule.console.name + target_id = "health_lambda" + arn = aws_lambda_function.health_lambda.arn +} + +resource "aws_lambda_permission" "health_lambda" { + statement_id = "AllowExecutionFromCloudWatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.health_lambda.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.console.arn +} diff --git a/main.tf b/main.tf index 73c2e89..3e07e29 100644 --- a/main.tf +++ b/main.tf @@ -14,34 +14,26 @@ locals { } resource "aws_cloudwatch_event_rule" "console" { - count = var.sns_topic_name != "" ? 1 : 0 - name = "AWSHealthEventRule" - description = "EventBridge rule for AWS Health events" - + name = var.event_rule_name + description = "EventBridge rule for AWS Health events" event_pattern = var.use_default_event_pattern ? jsonencode(local.default_event_pattern) : jsonencode(local.custom_event_pattern) } + resource "aws_sns_topic" "health_event_topic" { count = var.sns_topic_name != "" ? 1 : 0 kms_master_key_id = var.sns_kms_encryption ? aws_kms_key.sns[0].id : null # default key does not allow cloudwatch alarms to publish name = var.sns_topic_name } -resource "aws_cloudwatch_event_target" "sns" { - count = var.sns_topic_name != "" ? 1 : 0 - rule = aws_cloudwatch_event_rule.console[count.index].name - target_id = "SendToSNS" - arn = aws_sns_topic.health_event_topic[count.index].arn -} - resource "aws_sns_topic_subscription" "email_subscription" { - count = var.email_endpoint != null ? 1 : 0 + count = var.email_endpoint != "" && var.sns_topic_name != "" ? 1 : 0 topic_arn = aws_sns_topic.health_event_topic[count.index].arn protocol = "email" endpoint = var.email_endpoint } resource "aws_sns_topic_subscription" "webhook_subscription" { - count = var.webhook_endpoint != "" ? 1 : 0 + count = var.webhook_endpoint != "" && var.sns_topic_name != "" ? 1 : 0 topic_arn = aws_sns_topic.health_event_topic[count.index].arn protocol = "https" endpoint = var.webhook_endpoint diff --git a/outputs.tf b/outputs.tf index bc3c43f..9acf21e 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,7 +1,7 @@ output "event_bridge_rule_id" { - value = aws_cloudwatch_event_rule.console[0].id + value = aws_cloudwatch_event_rule.console.id } output "sns_topic_arn" { - value = aws_sns_topic.health_event_topic[0].arn + value = var.sns_topic_name != "" ? aws_sns_topic.health_event_topic[0].arn : var.sns_topic_arn } diff --git a/variables.tf b/variables.tf index 4d84409..c9892d6 100644 --- a/variables.tf +++ b/variables.tf @@ -1,11 +1,17 @@ +variable "event_rule_name" { + description = "Name for the AWS Health Event Rule" + type = string + default = "AWSHealthEventRule" +} + variable "email_endpoint" { - description = "Email address for notifications (optional)" + description = "Email address for notifications (optional) - only when sns_topic_name is set" type = string default = "" } variable "webhook_endpoint" { - description = "Webhook URL for notifications (optional)" + description = "Webhook URL for notifications (optional) - only when sns_topic_name is set" type = string default = "" } @@ -33,3 +39,9 @@ variable "sns_topic_name" { description = "Topic name (optional - creates SNS topic)" default = "" } + +variable "sns_topic_arn" { + type = string + description = "Topic ARN (optional - uses an existing SNS topic)" + default = "" +} diff --git a/versions.tf b/versions.tf index 657c3ea..0a4a03c 100644 --- a/versions.tf +++ b/versions.tf @@ -2,9 +2,9 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.0" + version = ">= 4.0" } } required_version = ">= 0.13.0" -} \ No newline at end of file +}