From bcc801f7d122c2f455f3f7e041cd0d04ee9a4faa Mon Sep 17 00:00:00 2001 From: Allan Denot Date: Fri, 26 Jul 2024 14:48:32 +1000 Subject: [PATCH] Fix email conditional to allow email-only or chatbot-only deployment --- _outputs.tf | 7 +----- _variables.tf | 18 ---------------- alarms.tf | 6 ++---- cloudtrail-alarms-full.cf.json | 39 +++++++++++++++++++--------------- cloudtrail-alarms-light.cf.yml | 10 +++++---- event_bridge.tf | 8 ++++--- iam.tf | 12 +++++++---- main.tf | 9 +++++--- sns.tf | 16 ++++++-------- 9 files changed, 57 insertions(+), 68 deletions(-) diff --git a/_outputs.tf b/_outputs.tf index 6f5b8a3..4611bfa 100644 --- a/_outputs.tf +++ b/_outputs.tf @@ -1,9 +1,4 @@ output "lambda_arn" { description = "The ARN from lambda custom message" - value = aws_lambda_function.lambda.arn -} - -output "alarm_sns_topic" { - description = "The SNS topic to which CloudWatch Alarms will be sent." - value = aws_sns_topic.alarms + value = aws_lambda_function.lambda[*].arn } diff --git a/_variables.tf b/_variables.tf index d881a80..ebfb11f 100644 --- a/_variables.tf +++ b/_variables.tf @@ -1,9 +1,3 @@ -variable "enabled" { - description = "The boolean flag whether this module is enabled or not. No resources are created when set to false." - type = bool - default = true -} - variable "lambda_name" { description = "The name of the lambda which will be notified with a custom message when any alarm is performed." type = string @@ -33,23 +27,11 @@ variable "chatbot_sns_topic" { default = "" } -# variable "email_sns_topic" { -# description = "The arn of the SNS Topic which will be notified when any alarm is performed via email." -# type = string -# default = "" -# } - variable "emails" { default = [] type = list(string) } - -variable "alarm_account_ids" { - default = [] - type = list(string) -} - variable "alarm_mode" { default = "light" type = string diff --git a/alarms.tf b/alarms.tf index 8b0564f..1a1bd8b 100644 --- a/alarms.tf +++ b/alarms.tf @@ -1,6 +1,5 @@ resource "random_string" "cloudtrail_alarm_suffix" { - count = var.enabled ? 1 : 0 length = 8 special = false lower = true @@ -9,12 +8,11 @@ resource "random_string" "cloudtrail_alarm_suffix" { } resource "aws_cloudformation_stack" "cloudtrail_alarm" { - count = var.enabled ? 1 : 0 - name = "cloudtrail-alarm-${random_string.cloudtrail_alarm_suffix[0].result}" + name = "cloudtrail-alarm-${random_string.cloudtrail_alarm_suffix.result}" template_body = var.alarm_mode == "full" ? file("${path.module}/cloudtrail-alarms-full.cf.json") : file("${path.module}/cloudtrail-alarms-light.cf.yml") parameters = { CloudTrailLogGroupName = var.cloudtrail_log_group_name - AlarmNotificationTopic = var.chatbot_sns_topic # aws_sns_topic.alarms[0].id + AlarmNotificationTopic = var.chatbot_sns_topic } } diff --git a/cloudtrail-alarms-full.cf.json b/cloudtrail-alarms-full.cf.json index de3bee2..c269ced 100644 --- a/cloudtrail-alarms-full.cf.json +++ b/cloudtrail-alarms-full.cf.json @@ -12,6 +12,11 @@ "Description": "SNS Notification Topic to send alarms to" } }, + "Conditions": { + "HasAlarmNotificationTopic": { + "Fn::Not": [{ "Fn::Equals": [{ "Ref": "AlarmNotificationTopic" }, ""] }] + } + }, "Resources" : { "SecurityGroupChangesMetricFilter": { "Type": "AWS::Logs::MetricFilter", @@ -31,7 +36,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailSecurityGroupChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, update or delete a Security Group.", "MetricName" : "SecurityGroupEventCount", "Namespace" : "CloudTrailMetrics", @@ -61,7 +66,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailNetworkAclChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, update or delete a Network ACL.", "MetricName" : "NetworkAclEventCount", "Namespace" : "CloudTrailMetrics", @@ -91,7 +96,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailGatewayChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, update or delete a Customer or Internet Gateway.", "MetricName" : "GatewayEventCount", "Namespace" : "CloudTrailMetrics", @@ -121,7 +126,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailVpcChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, update or delete a VPC, VPC peering connection or VPC connection to classic.", "MetricName" : "VpcEventCount", "Namespace" : "CloudTrailMetrics", @@ -151,7 +156,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailEC2InstanceChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, terminate, start, stop or reboot an EC2 instance.", "MetricName" : "EC2InstanceEventCount", "Namespace" : "CloudTrailMetrics", @@ -181,7 +186,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailEC2LargeInstanceChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, terminate, start, stop or reboot a 4x or 8x-large EC2 instance.", "MetricName" : "EC2LargeInstanceEventCount", "Namespace" : "CloudTrailMetrics", @@ -211,7 +216,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, update or delete a CloudTrail trail, or to start or stop logging to a trail.", "MetricName" : "CloudTrailEventCount", "Namespace" : "CloudTrailMetrics", @@ -242,7 +247,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailConsoleSignInFailuresV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an unauthenticated API call is made to sign into the console.", "MetricName" : "ConsoleSignInFailureCount", "Namespace" : "CloudTrailMetrics", @@ -273,7 +278,7 @@ "Properties": { "AlarmName" : "CloudTrailAuthorizationFailuresV2", "AlarmDescription" : "Alarms when an unauthorized API call is made.", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "MetricName" : "AuthorizationFailureCount", "Namespace" : "CloudTrailMetrics", "ComparisonOperator" : "GreaterThanOrEqualToThreshold", @@ -303,7 +308,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailIAMPolicyChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to change an IAM policy.", "MetricName" : "IAMPolicyEventCount", "Namespace" : "CloudTrailMetrics", @@ -333,7 +338,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CloudTrailRouteTableChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms when an API call is made to create, update or delete a Route Table.", "MetricName" : "RouteTableEventCount", "Namespace" : "CloudTrailMetrics", @@ -363,7 +368,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "CMKChangesV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms each time when a CMK configuration change is made.", "MetricName" : "CMKEventCount", "Namespace" : "CloudTrailMetrics", @@ -393,7 +398,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "RootAccountUsageAlarmV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Alarms each time when Root Account is used.", "MetricName" : "RootAccountUsageEventCount", "Namespace" : "CloudTrailMetrics", @@ -425,7 +430,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "AWSConfigChangesAlarmV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Triggered by AWS Config changes.", "MetricName" : "ConfigEventCount", "Namespace" : "CloudTrailMetrics", @@ -458,7 +463,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "S3BucketConfigChangesAlarmV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Triggered by AWS S3 Bucket config changes.", "MetricName" : "S3BucketEventCount", "Namespace" : "CloudTrailMetrics", @@ -492,7 +497,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "ConsoleSignInWithoutMfaAlarmV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Triggered by sign-in requests made without MFA.", "MetricName" : "ConsoleSignInWithoutMfaCount", "Namespace" : "CloudTrailMetrics", @@ -526,7 +531,7 @@ "Type": "AWS::CloudWatch::Alarm", "Properties": { "AlarmName" : "OrganizationsChangesAlarmV2", - "AlarmActions": [{"Ref" : "AlarmNotificationTopic"}], + "AlarmActions": { "Fn::If" : [ "HasAlarmNotificationTopic", [{"Ref" : "AlarmNotificationTopic"}], { "Ref": "AWS::NoValue" } ] }, "AlarmDescription" : "Triggered by AWS Organizations events.", "MetricName" : "OrganizationsEvents", "Namespace" : "CloudTrailMetrics", diff --git a/cloudtrail-alarms-light.cf.yml b/cloudtrail-alarms-light.cf.yml index 9ed8e93..f6077fb 100644 --- a/cloudtrail-alarms-light.cf.yml +++ b/cloudtrail-alarms-light.cf.yml @@ -11,6 +11,10 @@ Parameters: AlarmNotificationTopic: Type: String Description: SNS Notification Topic to send alarms to + Default: "" + +Conditions: + HasAlarmNotificationTopic: !Not [!Equals [!Ref AlarmNotificationTopic, ""]] #==================================================================================================== Resources: @@ -31,8 +35,7 @@ Resources: Properties: AlarmName: RootAccountUsage AlarmDescription: (CIS-3.3) Alarms when Root Account is used - AlarmActions: - - !Ref AlarmNotificationTopic + AlarmActions: !If [HasAlarmNotificationTopic, [!Ref AlarmNotificationTopic], !Ref AWS::NoValue] MetricName: RootAccountUsageEventCount Namespace: CloudTrailMetrics ComparisonOperator: GreaterThanOrEqualToThreshold @@ -57,8 +60,7 @@ Resources: Properties: AlarmName: NoMFALogin AlarmDescription: Monitoring for single-factor console logins will increase visibility into accounts that are not protected by MFA. - AlarmActions: - - !Ref AlarmNotificationTopic + AlarmActions: !If [HasAlarmNotificationTopic, [!Ref AlarmNotificationTopic], !Ref AWS::NoValue] MetricName: NoMFALoginEventCount Namespace: CloudTrailMetrics ComparisonOperator: GreaterThanOrEqualToThreshold diff --git a/event_bridge.tf b/event_bridge.tf index f655ce5..170da31 100644 --- a/event_bridge.tf +++ b/event_bridge.tf @@ -1,7 +1,7 @@ resource "aws_cloudwatch_event_rule" "alarm_notification" { + count = length(var.emails) > 0 ? 1 : 0 name = "cloudtrail_alarm_custom_notifications" description = "Will be notified with a custom message when any alarm is performed" - is_enabled = true event_pattern = < 0 ? 1 : 0 + rule = aws_cloudwatch_event_rule.alarm_notification[0].name target_id = "NotifyLambda" - arn = aws_lambda_function.lambda.arn + arn = aws_lambda_function.lambda[0].arn } diff --git a/iam.tf b/iam.tf index 655ef3d..16ef74b 100644 --- a/iam.tf +++ b/iam.tf @@ -1,4 +1,5 @@ data "aws_iam_policy_document" "lambda_assume_role" { + count = length(var.emails) > 0 ? 1 : 0 statement { actions = ["sts:AssumeRole"] principals { @@ -9,12 +10,14 @@ data "aws_iam_policy_document" "lambda_assume_role" { } resource "aws_iam_role" "iam_for_lambda" { + count = length(var.emails) > 0 ? 1 : 0 name = "cloudtrail-cn-role-${data.aws_region.current.name}" - assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json + assume_role_policy = data.aws_iam_policy_document.lambda_assume_role[0].json tags = var.tags } resource "aws_iam_policy" "lambda_cw" { + count = length(var.emails) > 0 ? 1 : 0 name = "cloudtrail-cn-policy-${data.aws_region.current.name}" path = "/" description = "IAM policy for logging from a lambda" @@ -32,7 +35,7 @@ resource "aws_iam_policy" "lambda_cw" { "logs:DescribeMetricFilters", "logs:FilterLogEvents" ], - Resource : [aws_lambda_function.lambda.arn, "arn:aws:logs:*:*:*", "arn:aws:cloudwatch:*:*:*"] + Resource : [aws_lambda_function.lambda[0].arn, "arn:aws:logs:*:*:*", "arn:aws:cloudwatch:*:*:*"] Effect : "Allow" }, { @@ -50,6 +53,7 @@ resource "aws_iam_policy" "lambda_cw" { } resource "aws_iam_role_policy_attachment" "lambda_cw" { - role = aws_iam_role.iam_for_lambda.name - policy_arn = aws_iam_policy.lambda_cw.arn + count = length(var.emails) > 0 ? 1 : 0 + role = aws_iam_role.iam_for_lambda[0].name + policy_arn = aws_iam_policy.lambda_cw[0].arn } diff --git a/main.tf b/main.tf index 5e8162b..a824140 100644 --- a/main.tf +++ b/main.tf @@ -1,7 +1,8 @@ resource "aws_lambda_function" "lambda" { + count = length(var.emails) > 0 ? 1 : 0 filename = "${path.module}/lambda.zip" function_name = var.lambda_name - role = aws_iam_role.iam_for_lambda.arn + role = aws_iam_role.iam_for_lambda[0].arn handler = "index.handler" timeout = var.lambda_timeout source_code_hash = filebase64sha256("${path.module}/lambda.zip") @@ -20,14 +21,16 @@ resource "aws_lambda_function" "lambda" { } resource "aws_lambda_permission" "default" { + count = length(var.emails) > 0 ? 1 : 0 statement_id = "AllowExecutionFromEventBridge" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda.function_name + function_name = aws_lambda_function.lambda[0].function_name principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.alarm_notification.arn + source_arn = aws_cloudwatch_event_rule.alarm_notification[0].arn } resource "aws_cloudwatch_log_group" "alarm_lambda" { + count = length(var.emails) > 0 ? 1 : 0 name = "/aws/lambda/${var.lambda_name}" retention_in_days = 365 tags = var.tags diff --git a/sns.tf b/sns.tf index 6f4fa97..0ddad30 100644 --- a/sns.tf +++ b/sns.tf @@ -2,7 +2,7 @@ # The SNS topic to which CloudWatch alarms send events. # -------------------------------------------------------------------------------------------------- resource "aws_sns_topic" "alarms" { - count = var.enabled ? 1 : 0 + count = length(var.emails) > 0 ? 1 : 0 name = var.sns_topic_name kms_master_key_id = var.kms_key #aws_kms_key.sns[0].id # default key does not allow cloudwatch alarms to publish tags = var.tags @@ -10,13 +10,13 @@ resource "aws_sns_topic" "alarms" { resource "aws_sns_topic_policy" "alarms" { - count = var.enabled ? 1 : 0 + count = length(var.emails) > 0 ? 1 : 0 arn = aws_sns_topic.alarms[0].arn policy = data.aws_iam_policy_document.alarms_policy[0].json } data "aws_iam_policy_document" "alarms_policy" { - count = var.enabled ? 1 : 0 + count = length(var.emails) > 0 ? 1 : 0 policy_id = "allow-org-accounts" statement { @@ -34,7 +34,7 @@ data "aws_iam_policy_document" "alarms_policy" { condition { test = "StringEquals" variable = "AWS:SourceOwner" - values = var.alarm_account_ids + values = [data.aws_caller_identity.current.account_id] } principals { type = "AWS" @@ -45,11 +45,9 @@ data "aws_iam_policy_document" "alarms_policy" { } } - -resource "aws_sns_topic_subscription" "cloudtrail_cutom_alarm_email" { - #for_each = {for email in var.emails : var.emails => email} - for_each = toset(var.emails) +resource "aws_sns_topic_subscription" "cloudtrail_custom_alarm_email" { + for_each = toset(var.emails) topic_arn = aws_sns_topic.alarms[0].arn protocol = "email" endpoint = each.value -} \ No newline at end of file +}