😐

【Terraform】AWS Security HubのCIS用ログメトリクスフィルター・アラーム

2022/11/06に公開

概要

AWS Security Hub がサポートしている CIS AWS Foundations Benchmark のコントロールの項目 3.1 ~ 3.14 で必要なログメトリクスフィルターとアラームについての Terraform のコードです。
それらのリソースは、量が多くて作成するのが面倒かなと思いきや、名前とフィルターパターンが異なるだけだったので、for_eachで簡単に作成できます。

環境

Terraform 1.2.5
Terraform AWS Provider 4.22.0

前提

以下のリソースはすでに存在している想定です。

  • CloudTrail ログの出力先の CloudWatch ロググループ
  • CIS アラームを受信する SNS トピック

コード

local.tf
locals {
  alarms = {
    "CIS-3.1-UnauthorizedAPICalls"         = "{($.errorCode=\"*UnauthorizedOperation\") || ($.errorCode=\"AccessDenied*\")}"
    "CIS-3.2-ConsoleSigninWithoutMFA"      = "{($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") && ($.userIdentity.type = \"IAMUser\") && ($.responseElements.ConsoleLogin = \"Success\") }"
    "CIS-3.3-RootAccountUsage"             = "{$.userIdentity.type=\"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !=\"AwsServiceEvent\"}"
    "CIS-3.4-IAMPolicyChanges"             = "{($.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)}"
    "CIS-3.5-CloudTrailChanges"            = "{($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging)}"
    "CIS-3.6-ConsoleAuthenticationFailure" = "{($.eventName=ConsoleLogin) && ($.errorMessage=\"Failed authentication\")}"
    "CIS-3.7-DisableOrDeleteCMK"           = "{($.eventSource=kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion))}"
    "CIS-3.8-S3BucketPolicyChanges"        = "{($.eventSource=s3.amazonaws.com) && (($.eventName=PutBucketAcl) || ($.eventName=PutBucketPolicy) || ($.eventName=PutBucketCors) || ($.eventName=PutBucketLifecycle) || ($.eventName=PutBucketReplication) || ($.eventName=DeleteBucketPolicy) || ($.eventName=DeleteBucketCors) || ($.eventName=DeleteBucketLifecycle) || ($.eventName=DeleteBucketReplication))}"
    "CIS-3.9-AWSConfigChanges"             = "{($.eventSource=config.amazonaws.com) && (($.eventName=StopConfigurationRecorder) || ($.eventName=DeleteDeliveryChannel) || ($.eventName=PutDeliveryChannel) || ($.eventName=PutConfigurationRecorder))}"
    "CIS-3.10-SecurityGroupChanges"        = "{($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup)}"
    "CIS-3.11-NetworkACLChanges"           = "{($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation)}"
    "CIS-3.12-NetworkGatewayChanges"       = "{($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway)}"
    "CIS-3.13-RouteTableChanges"           = "{($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable)}"
    "CIS-3.14-VPCChanges"                  = "{($.eventName=CreateVpc) || ($.eventName=DeleteVpc) || ($.eventName=ModifyVpcAttribute) || ($.eventName=AcceptVpcPeeringConnection) || ($.eventName=CreateVpcPeeringConnection) || ($.eventName=DeleteVpcPeeringConnection) || ($.eventName=RejectVpcPeeringConnection) || ($.eventName=AttachClassicLinkVpc) || ($.eventName=DetachClassicLinkVpc) || ($.eventName=DisableVpcClassicLink) || ($.eventName=EnableVpcClassicLink)}"
  }
}
security_hub.tf
resource "aws_cloudwatch_log_metric_filter" "example" {
  for_each = local.alarms

  name           = each.key
  pattern        = each.value
  log_group_name = aws_cloudwatch_log_group.example.name

  metric_transformation {
    name      = each.key
    namespace = "LogMetrics"
    value     = "1"
  }
}

resource "aws_cloudwatch_metric_alarm" "example" {
  for_each = local.alarms

  alarm_name          = each.key
  alarm_description   = each.key
  statistic           = "Average"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  threshold           = 1
  period              = 300
  evaluation_periods  = 1
  metric_name         = each.key
  namespace           = "LogMetrics"
  treat_missing_data  = "notBreaching"
  alarm_actions       = [aws_sns_topic.example.arn]
  ok_actions          = [aws_sns_topic.example.arn]
}

おまけ

検証環境とステージング環境は同じ AWS アカウントで、本番環境だけは別の AWS アカウントである場合があるかもしれません。
その場合、検証環境とステージング環境は同じ AWS アカウントであるため、ログメトリクスフィルターとアラームが重複してしまいます。
重複して作成されないようにするには、以下のとおりfor_eachの部分をいじって、if文で検証環境か本番環境の場合にのみリソースが作成されるようにします。[1]

security_hub.tf
resource "aws_cloudwatch_log_metric_filter" "example" {
  for_each = { for key, value in local.alarms : key => value if var.common["env"] == "development" || var.common["env"] == "production" }

  name           = each.key
  pattern        = each.value
  log_group_name = aws_cloudwatch_log_group.example.name

  metric_transformation {
    name      = each.key
    namespace = "LogMetrics"
    value     = "1"
  }
}

resource "aws_cloudwatch_metric_alarm" "example" {
  for_each = { for key, value in local.alarms : key => value if var.common["env"] == "development" || var.common["env"] == "production" }

  alarm_name          = each.key
  alarm_description   = each.key
  statistic           = "Average"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  threshold           = 1
  period              = 300
  evaluation_periods  = 1
  metric_name         = each.key
  namespace           = "LogMetrics"
  treat_missing_data  = "notBreaching"
  alarm_actions       = [aws_sns_topic.example.arn]
  ok_actions          = [aws_sns_topic.example.arn]
}
脚注
  1. もっと良い書き方があるかもしれません。もしあればぜひ教えていただきたいです。 ↩︎

Discussion