From ff38d9b13de7d8e9655f668c2f1b1b57c655a5dd Mon Sep 17 00:00:00 2001 From: Luke Fritz Date: Thu, 8 Jul 2021 17:26:28 -0500 Subject: [PATCH 1/3] EN-471: Add CloudTrail alarms for CIS benchmark (feat) --- examples/aws-cloudtrail-alarms/main.tf | 22 + examples/aws-cloudtrail-alarms/vars.tf | 11 + modules/aws-cloudtrail-monitoring/main.tf | 612 +++++++++++++++++++ modules/aws-cloudtrail-monitoring/outputs.tf | 24 + modules/aws-cloudtrail-monitoring/vars.tf | 123 ++++ modules/aws-cloudtrail/main.tf | 7 + modules/aws-cloudtrail/vars.tf | 20 + 7 files changed, 819 insertions(+) create mode 100644 examples/aws-cloudtrail-alarms/main.tf create mode 100644 examples/aws-cloudtrail-alarms/vars.tf create mode 100644 modules/aws-cloudtrail-monitoring/main.tf create mode 100644 modules/aws-cloudtrail-monitoring/outputs.tf create mode 100644 modules/aws-cloudtrail-monitoring/vars.tf diff --git a/examples/aws-cloudtrail-alarms/main.tf b/examples/aws-cloudtrail-alarms/main.tf new file mode 100644 index 0000000..9b4a0a6 --- /dev/null +++ b/examples/aws-cloudtrail-alarms/main.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "3.22.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + + +module "aws_cloudtrail_alarms" { + source = "../../modules/aws-cloudtrail-alarms" + + cloudwatch_log_group_name = var.cloudwatch_log_group_name + kms_master_key_id = "--TODO--" +} diff --git a/examples/aws-cloudtrail-alarms/vars.tf b/examples/aws-cloudtrail-alarms/vars.tf new file mode 100644 index 0000000..891479c --- /dev/null +++ b/examples/aws-cloudtrail-alarms/vars.tf @@ -0,0 +1,11 @@ +variable "aws_region" { + description = "The AWS region in which this module is deployed." + type = string + default = "us-east-1" +} + +variable "cloudwatch_log_group_name" { + description = "The name of the CloudWatch Logs group to which CloudTrail events are delivered." + type = string + default = "example-cloudtrail-log-group" +} diff --git a/modules/aws-cloudtrail-monitoring/main.tf b/modules/aws-cloudtrail-monitoring/main.tf new file mode 100644 index 0000000..24de11b --- /dev/null +++ b/modules/aws-cloudtrail-monitoring/main.tf @@ -0,0 +1,612 @@ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CREATE CLOUDWATCH ALARMS FOR CLOUDTRAIL EVENTS +# Create CloudWatch alarms that are triggered from events streaming from CloudTrail. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +terraform { + required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.49.0" + } + } +} + +# -------------------------------------------------------------------------------------------------- +# CREATE THE SNS TOPIC TO WHICH ALARMS WILL BE FORWARDED +# -------------------------------------------------------------------------------------------------- + +resource "aws_sns_topic" "alarms" { + name = var.sns_topic_name + kms_master_key_id = var.kms_master_key_id + tags = var.tags +} + +# -------------------------------------------------------------------------------------------------- +# CREATE AN SNS TOPIC POLICY +# Create an SNS topic policy that allows the CloudWatch alarms in each of the org accounts to send +# alarm details to this SNS topic (typically only deployed in the audit account). +# -------------------------------------------------------------------------------------------------- + +resource "aws_sns_topic_policy" "policy" { + arn = aws_sns_topic.alarms.arn + policy = data.aws_iam_policy_document.sns_topic_policy.json +} + +data "aws_iam_policy_document" "sns_topic_policy" { + statement { + sid = "AllowCloudWatchAlarmsSnsPublishAccess" + effect = "Allow" + + principals { + type = "Service" + identifiers = ["cloudwatch.amazonaws.com"] + } + + actions = [ + "sns:Publish" + ] + + resources = [aws_sns_topic.alarms.arn] + } +} + +# ---------------------------------------------------------------------------------------------------------------------- +# CREATE THE CLOUDWATCH LOG GROUP WHERE CLOUDTRAIL EVENTS WILL BE SENT +# ---------------------------------------------------------------------------------------------------------------------- + +resource "aws_cloudwatch_log_group" "events" { + name = var.cloudwatch_log_group_name + kms_key_id = var.kms_master_key_id + retention_in_days = var.cloudwatch_retention_in_days +} + +# ---------------------------------------------------------------------------------------------------------------------- +# CREATE AN IAM ROLE TO ALLOW CLOUDTRAIL TO SHIP LOGS TO CLOUDWATCH LOGS +# ---------------------------------------------------------------------------------------------------------------------- + +resource "aws_iam_service_linked_role" "cloudwatch_service_linked_role" { + aws_service_name = "cloudwatch-crossaccount.amazonaws.com" +} + +# ---------------------------------------------------------------------------------------------------------------------- +# CREATE AN IAM ROLE TO ALLOW CLOUDTRAIL TO SHIP LOGS TO CLOUDWATCH LOGS +# ---------------------------------------------------------------------------------------------------------------------- + +resource "aws_iam_role" "cloudwatch_logs_role" { + name = "allow-cloudtrail-cloudwatch-logs-write-access" + + assume_role_policy = data.aws_iam_policy_document.cloudwatch_logs_assume_role_policy.json + + inline_policy { + name = "AllowCloudWatchLogsAccess" + policy = data.aws_iam_policy_document.cloudwatch_logs_policy.json + } +} + +data "aws_iam_policy_document" "cloudwatch_logs_assume_role_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["cloudtrail.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "cloudwatch_logs_policy" { + statement { + effect = "Allow" + + actions = [ + "logs:DescribeLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + ] + + resources = [aws_cloudwatch_log_group.events.arn] + } +} + +# ---------------------------------------------------------------------------------------------------------------------- +# CREATE CLOUDWATCH ALARMS TO MONITOR GRC CONTROLS +# ---------------------------------------------------------------------------------------------------------------------- + +resource "aws_cloudwatch_log_metric_filter" "unauthorized_api_calls" { + count = var.unauthorized_api_calls_enabled ? 1 : 0 + + name = "UnauthorizedAPICalls" + pattern = "{ ($.errorCode = \"*UnauthorizedOperation\") || ($.errorCode = \"AccessDenied*\") }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "UnauthorizedAPICalls" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "unauthorized_api_calls" { + count = var.unauthorized_api_calls_enabled ? 1 : 0 + + alarm_name = "UnauthorizedAPICalls" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.unauthorized_api_calls[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "no_mfa_console_signin" { + count = var.no_mfa_console_signin_enabled ? 1 : 0 + + name = "NoMFAConsoleSignin" + pattern = "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "NoMFAConsoleSignin" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "no_mfa_console_signin" { + count = var.no_mfa_console_signin_enabled ? 1 : 0 + + alarm_name = "NoMFAConsoleSignin" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.no_mfa_console_signin[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring for single-factor console logins will increase visibility into accounts that are not protected by MFA." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "root_usage" { + count = var.root_usage_enabled ? 1 : 0 + + name = "RootUsage" + pattern = "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "RootUsage" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "root_usage" { + count = var.root_usage_enabled ? 1 : 0 + + alarm_name = "RootUsage" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.root_usage[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring for root account logins will provide visibility into the use of a fully privileged account and an opportunity to reduce the use of it." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "iam_changes" { + count = var.iam_changes_enabled ? 1 : 0 + + name = "IAMChanges" + pattern = "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "IAMChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "iam_changes" { + count = var.iam_changes_enabled ? 1 : 0 + + alarm_name = "IAMChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.iam_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to IAM policies will help ensure authentication and authorization controls remain intact." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "cloudtrail_cfg_changes" { + count = var.cloudtrail_cfg_changes_enabled ? 1 : 0 + + name = "CloudTrailCfgChanges" + pattern = "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "CloudTrailCfgChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "cloudtrail_cfg_changes" { + count = var.cloudtrail_cfg_changes_enabled ? 1 : 0 + + alarm_name = "CloudTrailCfgChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.cloudtrail_cfg_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to CloudTrail's configuration will help ensure sustained visibility to activities performed in the AWS account." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "console_signin_failures" { + count = var.console_signin_failures_enabled ? 1 : 0 + + name = "ConsoleSigninFailures" + pattern = "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "ConsoleSigninFailures" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "console_signin_failures" { + count = var.console_signin_failures_enabled ? 1 : 0 + + alarm_name = "ConsoleSigninFailures" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.console_signin_failures[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring failed console logins may decrease lead time to detect an attempt to brute force a credential, which may provide an indicator, such as source IP, that can be used in other event correlation." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "disable_or_delete_cmk" { + count = var.disable_or_delete_cmk_enabled ? 1 : 0 + + name = "DisableOrDeleteCMK" + pattern = "{ ($.eventSource = kms.amazonaws.com) && (($.eventName = DisableKey) || ($.eventName = ScheduleKeyDeletion)) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "DisableOrDeleteCMK" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "disable_or_delete_cmk" { + count = var.disable_or_delete_cmk_enabled ? 1 : 0 + + alarm_name = "DisableOrDeleteCMK" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.disable_or_delete_cmk[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring failed console logins may decrease lead time to detect an attempt to brute force a credential, which may provide an indicator, such as source IP, that can be used in other event correlation." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "s3_bucket_policy_changes" { + count = var.s3_bucket_policy_changes_enabled ? 1 : 0 + + name = "S3BucketPolicyChanges" + pattern = "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "S3BucketPolicyChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "s3_bucket_policy_changes" { + count = var.s3_bucket_policy_changes_enabled ? 1 : 0 + + alarm_name = "S3BucketPolicyChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.s3_bucket_policy_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to S3 bucket policies may reduce time to detect and correct permissive policies on sensitive S3 buckets." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "aws_config_changes" { + count = var.aws_config_changes_enabled ? 1 : 0 + + name = "AWSConfigChanges" + pattern = "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "AWSConfigChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "aws_config_changes" { + count = var.aws_config_changes_enabled ? 1 : 0 + + alarm_name = "AWSConfigChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.aws_config_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to AWS Config configuration will help ensure sustained visibility of configuration items within the AWS account." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "security_group_changes" { + count = var.security_group_changes_enabled ? 1 : 0 + + name = "SecurityGroupChanges" + pattern = "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "SecurityGroupChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "security_group_changes" { + count = var.security_group_changes_enabled ? 1 : 0 + + alarm_name = "SecurityGroupChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.security_group_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to security group will help ensure that resources and services are not unintentionally exposed." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "nacl_changes" { + count = var.nacl_changes_enabled ? 1 : 0 + + name = "NACLChanges" + pattern = "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "NACLChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "nacl_changes" { + count = var.nacl_changes_enabled ? 1 : 0 + + alarm_name = "NACLChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.nacl_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to NACLs will help ensure that AWS resources and services are not unintentionally exposed." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "network_gw_changes" { + count = var.network_gw_changes_enabled ? 1 : 0 + + name = "NetworkGWChanges" + pattern = "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "NetworkGWChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "network_gw_changes" { + count = var.network_gw_changes_enabled ? 1 : 0 + + alarm_name = "NetworkGWChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.network_gw_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to network gateways will help ensure that all ingress/egress traffic traverses the VPC border via a controlled path." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "route_table_changes" { + count = var.route_table_changes_enabled ? 1 : 0 + + name = "RouteTableChanges" + pattern = "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "RouteTableChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "route_table_changes" { + count = var.route_table_changes_enabled ? 1 : 0 + + alarm_name = "RouteTableChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.route_table_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "vpc_changes" { + count = var.vpc_changes_enabled ? 1 : 0 + + name = "VPCChanges" + pattern = "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "VPCChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "vpc_changes" { + count = var.vpc_changes_enabled ? 1 : 0 + + alarm_name = "VPCChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.vpc_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring changes to VPC will help ensure that all VPC traffic flows through an expected path." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} + +resource "aws_cloudwatch_log_metric_filter" "organizations_changes" { + count = var.organizations_changes_enabled ? 1 : 0 + + name = "OrganizationsChanges" + pattern = "{ ($.eventSource = organizations.amazonaws.com) && (($.eventName = \"AcceptHandshake\") || ($.eventName = \"AttachPolicy\") || ($.eventName = \"CreateAccount\") || ($.eventName = \"CreateOrganizationalUnit\") || ($.eventName= \"CreatePolicy\") || ($.eventName = \"DeclineHandshake\") || ($.eventName = \"DeleteOrganization\") || ($.eventName = \"DeleteOrganizationalUnit\") || ($.eventName = \"DeletePolicy\") || ($.eventName = \"DetachPolicy\") || ($.eventName = \"DisablePolicyType\") || ($.eventName = \"EnablePolicyType\") || ($.eventName = \"InviteAccountToOrganization\") || ($.eventName = \"LeaveOrganization\") || ($.eventName = \"MoveAccount\") || ($.eventName = \"RemoveAccountFromOrganization\") || ($.eventName = \"UpdatePolicy\") || ($.eventName =\"UpdateOrganizationalUnit\")) }" + log_group_name = aws_cloudwatch_log_group.events.name + + metric_transformation { + name = "OrganizationsChanges" + namespace = var.alarm_namespace + value = "1" + } +} + +resource "aws_cloudwatch_metric_alarm" "organizations_changes" { + count = var.organizations_changes_enabled ? 1 : 0 + + alarm_name = "OrganizationsChanges" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = "1" + metric_name = aws_cloudwatch_log_metric_filter.organizations_changes[0].id + namespace = var.alarm_namespace + period = "300" + statistic = "Sum" + threshold = "1" + alarm_description = "Monitoring AWS Organizations changes can help you prevent any unwanted, accidental or intentional modifications that may lead to unauthorized access or other security breaches." + alarm_actions = [aws_sns_topic.alarms.arn] + treat_missing_data = "notBreaching" + insufficient_data_actions = [] + + tags = var.tags +} diff --git a/modules/aws-cloudtrail-monitoring/outputs.tf b/modules/aws-cloudtrail-monitoring/outputs.tf new file mode 100644 index 0000000..4648364 --- /dev/null +++ b/modules/aws-cloudtrail-monitoring/outputs.tf @@ -0,0 +1,24 @@ +output "sns_topic_name" { + description = "The name of the SNS topic to which alarms will be forwarded." + value = aws_sns_topic.alarms.name +} + +output "sns_topic_arn" { + description = "The ARN of the SNS topic to which alarms will be forwarded." + value = aws_sns_topic.alarms.arn +} + +output "cloudwatch_log_group_name" { + description = "The name of the CloudWatch log group to which CloudTrail logs will be sent." + value = aws_cloudwatch_log_group.events.name +} + +output "cloudwatch_log_group_arn" { + description = "The ARN of the CloudWatch log group to which CloudTrail logs will be sent." + value = aws_cloudwatch_log_group.events.arn +} + +output "cloudwatch_role_arn" { + description = "The ARN of the IAM role used by CloudTrail to forward events to CloudWatch." + value = aws_iam_role.cloudwatch_logs_role.arn +} diff --git a/modules/aws-cloudtrail-monitoring/vars.tf b/modules/aws-cloudtrail-monitoring/vars.tf new file mode 100644 index 0000000..b7d0ef6 --- /dev/null +++ b/modules/aws-cloudtrail-monitoring/vars.tf @@ -0,0 +1,123 @@ +variable "unauthorized_api_calls_enabled" { + description = "The boolean flag whether the unauthorized_api_calls alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "no_mfa_console_signin_enabled" { + description = "The boolean flag whether the no_mfa_console_signin alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "root_usage_enabled" { + description = "The boolean flag whether the root_usage alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "iam_changes_enabled" { + description = "The boolean flag whether the iam_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "cloudtrail_cfg_changes_enabled" { + description = "The boolean flag whether the cloudtrail_cfg_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "console_signin_failures_enabled" { + description = "The boolean flag whether the console_signin_failures alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "disable_or_delete_cmk_enabled" { + description = "The boolean flag whether the disable_or_delete_cmk alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "s3_bucket_policy_changes_enabled" { + description = "The boolean flag whether the s3_bucket_policy_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "aws_config_changes_enabled" { + description = "The boolean flag whether the aws_config_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "security_group_changes_enabled" { + description = "The boolean flag whether the security_group_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "nacl_changes_enabled" { + description = "The boolean flag whether the nacl_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "network_gw_changes_enabled" { + description = "The boolean flag whether the network_gw_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "route_table_changes_enabled" { + description = "The boolean flag whether the route_table_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "vpc_changes_enabled" { + description = "The boolean flag whether the vpc_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "organizations_changes_enabled" { + description = "The boolean flag whether the organizations_changes alarm is enabled or not. No resources are created when set to false." + type = bool + default = true +} + +variable "alarm_namespace" { + description = "The namespace in which all alarms are set up." + type = string + default = "CloudTrailMonitoringAlarms" +} + +variable "cloudwatch_log_group_name" { + description = "The name of the CloudWatch Logs group to which CloudTrail events are delivered." + type = string +} + +variable "tags" { + description = "A key-value map of tags to apply to this resource." + type = map(string) + default = {} +} + +variable "kms_master_key_id" { + description = "The ID or alias of the KMS CMK used to encrypt the SNS topic." + type = string +} + +variable "cloudwatch_retention_in_days" { + description = "The number of days to retain CloudTrail events shipped to CloudWatch." + type = number + default = 30 +} + +variable "sns_topic_name" { + description = "The name of the SNS topic created to receive CloudWatch alarms." + type = string + default = "cloudtrail-monitoring-alarms" +} diff --git a/modules/aws-cloudtrail/main.tf b/modules/aws-cloudtrail/main.tf index ac3c97a..64160ee 100644 --- a/modules/aws-cloudtrail/main.tf +++ b/modules/aws-cloudtrail/main.tf @@ -70,6 +70,13 @@ resource "aws_cloudtrail" "cloudtrail" { is_organization_trail = local.is_organization_trail + # Specify the CloudWatch log group to which CloudTrail events should be shipped. + # The ARN needs to be suffixed with `:*` for legacy compatibility reasons. + # See: https://github.com/hashicorp/terraform-provider-aws/issues/14557#issuecomment-671975672 + cloud_watch_logs_group_arn = "${var.cloudwatch_log_group_arn}:*" + + cloud_watch_logs_role_arn = var.cloudwatch_logs_role_arn + tags = local.tags # Specifies the name of the Amazon SNS topic defined for notification of log file delivery. diff --git a/modules/aws-cloudtrail/vars.tf b/modules/aws-cloudtrail/vars.tf index 6504819..3c45f30 100644 --- a/modules/aws-cloudtrail/vars.tf +++ b/modules/aws-cloudtrail/vars.tf @@ -7,6 +7,16 @@ variable "name" { type = string } +variable "aws_region" { + description = "The AWS region in which these resources are provisioned." + type = string +} + +variable "aws_account_id" { + description = "The AWS account number in which these resources are provisioned." + type = string +} + # --------------------------------------------------------------------------------------------------------------------- # OPTIONAL MODULE PARAMETERS # --------------------------------------------------------------------------------------------------------------------- @@ -189,3 +199,13 @@ variable "s3_key_prefix" { type = string default = null } + +variable "cloudwatch_log_group_arn" { + description = "The ARN of the CloudWatch log group to which CloudTrail logs will be shipped." + type = string +} + +variable "cloudwatch_logs_role_arn" { + description = "The ARN of the IAM role assumed by CloudTrail permitting it to send events to CloudWatch Logs." + type = string +} From 8437b83ad531fee5f38755bc3b6b5d99eabefe63 Mon Sep 17 00:00:00 2001 From: Luke Fritz Date: Wed, 21 Jul 2021 16:02:59 -0500 Subject: [PATCH 2/3] EN-471: Remove example from previous module implementation (patch) --- examples/aws-cloudtrail-alarms/main.tf | 22 ---------------------- examples/aws-cloudtrail-alarms/vars.tf | 11 ----------- 2 files changed, 33 deletions(-) delete mode 100644 examples/aws-cloudtrail-alarms/main.tf delete mode 100644 examples/aws-cloudtrail-alarms/vars.tf diff --git a/examples/aws-cloudtrail-alarms/main.tf b/examples/aws-cloudtrail-alarms/main.tf deleted file mode 100644 index 9b4a0a6..0000000 --- a/examples/aws-cloudtrail-alarms/main.tf +++ /dev/null @@ -1,22 +0,0 @@ -terraform { - required_version = ">= 0.13.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = "3.22.0" - } - } -} - -provider "aws" { - region = var.aws_region -} - - -module "aws_cloudtrail_alarms" { - source = "../../modules/aws-cloudtrail-alarms" - - cloudwatch_log_group_name = var.cloudwatch_log_group_name - kms_master_key_id = "--TODO--" -} diff --git a/examples/aws-cloudtrail-alarms/vars.tf b/examples/aws-cloudtrail-alarms/vars.tf deleted file mode 100644 index 891479c..0000000 --- a/examples/aws-cloudtrail-alarms/vars.tf +++ /dev/null @@ -1,11 +0,0 @@ -variable "aws_region" { - description = "The AWS region in which this module is deployed." - type = string - default = "us-east-1" -} - -variable "cloudwatch_log_group_name" { - description = "The name of the CloudWatch Logs group to which CloudTrail events are delivered." - type = string - default = "example-cloudtrail-log-group" -} From 0195529ebb79a3ab2795245d024a43816a36a526 Mon Sep 17 00:00:00 2001 From: Luke Fritz Date: Wed, 21 Jul 2021 16:04:04 -0500 Subject: [PATCH 3/3] EN-471: Remove service role used for testing (patch) --- modules/aws-cloudtrail-monitoring/main.tf | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/aws-cloudtrail-monitoring/main.tf b/modules/aws-cloudtrail-monitoring/main.tf index 24de11b..86d3d24 100644 --- a/modules/aws-cloudtrail-monitoring/main.tf +++ b/modules/aws-cloudtrail-monitoring/main.tf @@ -67,14 +67,6 @@ resource "aws_cloudwatch_log_group" "events" { # CREATE AN IAM ROLE TO ALLOW CLOUDTRAIL TO SHIP LOGS TO CLOUDWATCH LOGS # ---------------------------------------------------------------------------------------------------------------------- -resource "aws_iam_service_linked_role" "cloudwatch_service_linked_role" { - aws_service_name = "cloudwatch-crossaccount.amazonaws.com" -} - -# ---------------------------------------------------------------------------------------------------------------------- -# CREATE AN IAM ROLE TO ALLOW CLOUDTRAIL TO SHIP LOGS TO CLOUDWATCH LOGS -# ---------------------------------------------------------------------------------------------------------------------- - resource "aws_iam_role" "cloudwatch_logs_role" { name = "allow-cloudtrail-cloudwatch-logs-write-access"