Thursday, January 9, 2020

Working with Cloudwatch Event Rules in Terraform

Terraform Notes

Working with Cloudwatch Event Rules

References:


Main file

This is a generic main terraform file that calls the module folder that contains the remainder of the terraform files to be used.

main.tf
provider "aws" {
  region = "us-east-2"
}

module "cw_event_rules" {
  source = "./modules/security/cw_event_rules"
}

Doing it inline

In order to use JSON string within your tf files, you must start with "<<PATTERN" and end with "PATTERN"

/modules/security/cw_event_rules/rules_inline.tf
resource "aws_cloudwatch_event_rule" "console" {
  name        = "capture-aws-sign-in"
  description = "Capture each AWS Console Sign In"

  event_pattern = <<PATTERN
{
  "detail-type": [
    "AWS Console Sign In via CloudTrail"
  ]
}
PATTERN
}

terraform plan
  # module.cw_event_rules.aws_cloudwatch_event_rule.console will be created
  + resource "aws_cloudwatch_event_rule" "console" {
      + arn           = (known after apply)
      + description   = "Capture each AWS Console Sign In"
      + event_pattern = jsonencode(
            {
              + detail-type = [
                  + "AWS Console Sign In via CloudTrail",
                ]
            }
        )
      + id            = (known after apply)
      + is_enabled    = true
      + name          = "capture-aws-sign-in"
    }

Pulling it in via local_file variable

This method uses a local_file data to pull the content of a file. After local_file is declared, you can call this via data.local_file.yourname.content. This is especially useful if your JSON is large and will probably hinder the readability of your tf file.

/modules/security/cw_event_rules/rules_external.tf
resource "aws_cloudwatch_event_rule" "console_external" {
  name        = "capture-aws-sign-in"
  description = "Capture each AWS Console Sign In"
  event_pattern = data.local_file.event_rule_console.content
}

/modules/security/cw_event_rules/rules_variables.tf
data "local_file" "event_rule_console" {
    filename = "${path.module}/jsons/event_rule_console.json"
}

/modules/security/cw_event_rules/jsons/event_rule_console.json
{
  "detail-type": [
    "AWS Console Sign In via CloudTrail"
  ]
}

result of terraform plan
  # module.cw_event_rules.aws_cloudwatch_event_rule.console_external will be created
  + resource "aws_cloudwatch_event_rule" "console_external" {
      + arn           = (known after apply)
      + description   = "Capture each AWS Console Sign In"
      + event_pattern = jsonencode(
            {
              + detail-type = [
                  + "AWS Console Sign In via CloudTrail",
                ]
            }
        )
      + id            = (known after apply)
      + is_enabled    = true
      + name          = "capture-aws-sign-in"
    }

Pulling it from inside Resources while using List of Maps

This method will NOT rely on the local_file, but instead pull the content directly from the resources block. One of the reason, I did this was I could not fit ${path.module} inside the list-map variable without error.

/modules/security/cw_event_rules/rules_map.tf
resource "aws_cloudwatch_event_rule" "all_event_rules_map" {
    count = length(var.event_rules)
    name = var.event_rules[count.index].name
    description = var.event_rules[count.index].description
    event_pattern = file("${path.module}/jsons/${var.event_rules[count.index].filename}")
}


/modules/security/cw_event_rules/rules_variables.tf
variable "event_rules" {
    description = "Create event rules with following names"
    type = list(map(string))
    default = [
        {
            "name"="CIS-UnauthorizedActivityAttempt",
            "description"="This is the description of 3.1 CIS",
            "filename"="CIS-UnauthorizedActivityAttempt.json"
        }
        {
            "name"="CIS-ConsoleLoginNoMFA",
            "description"="This is the description of 3.2 CIS",
            "filename"="CIS-ConsoleLoginNoMFA.json"
        }
    ]
}

result of terraform plan
  # module.cw_event_rules.aws_cloudwatch_event_rule.all_event_rules_map[0] will be created
  + resource "aws_cloudwatch_event_rule" "all_event_rules_map" {
      + arn           = (known after apply)
      + description   = "This is the description of 3.1 CIS"
      + event_pattern = jsonencode(
            {
              + detail      = {
                  + errorCode = [
                      + "Client.UnauthorizedOperation",
                      + "AccessDenied",
                    ]
                }
              + detail-type = [
                  + "AWS Console Sign In via CloudTrail",
                ]
            }
        )
      + id            = (known after apply)
      + is_enabled    = true
      + name          = "CIS-UnauthorizedActivityAttempt"
    }


  # module.cw_event_rules.aws_cloudwatch_event_rule.all_event_rules_map[1] will be created
  + resource "aws_cloudwatch_event_rule" "all_event_rules_map" {
      + arn           = (known after apply)
      + description   = "This is the description of 3.2 CIS"
      + event_pattern = jsonencode(
            {
              + detail = {
                  + additionalEventData = {
                      + MFAUsed = [
                          + "No",
                        ]
                    }
                  + eventSource         = [
                      + "signin.amazonaws.com",
                    ]
                  + responseElements    = {
                      + ConsoleLogin = [
                          + "Success",
                        ]
                    }
                }
            }
        )
      + id            = (known after apply)
      + is_enabled    = true
      + name          = "CIS-ConsoleLoginNoMFA"
    }

Let's add SNS topic as Cloudwatch Events' Target

We're only doing this to List-Map version of the code above. The SNS build code are directly from Terraform doc. Only part that has been edited are the mapping the cloudwatch event rules to target.

/modules/security/cw_event_rules/target.tf
resource "aws_cloudwatch_event_target" "all_event_rules_map" {
    count = length(aws_cloudwatch_event_rule.all_event_rules_map)
    rule      = aws_cloudwatch_event_rule.all_event_rules_map[count.index].name
    target_id = "SendToSNS"
    arn       = "${aws_sns_topic.aws_logins.arn}"
}

resource "aws_sns_topic" "aws_logins" {
  name = "aws-console-logins"
}

resource "aws_sns_topic_policy" "default" {
  arn    = "${aws_sns_topic.aws_logins.arn}"
  policy = "${data.aws_iam_policy_document.sns_topic_policy.json}"
}

data "aws_iam_policy_document" "sns_topic_policy" {
  statement {
    effect  = "Allow"
    actions = ["SNS:Publish"]

    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }

    resources = ["${aws_sns_topic.aws_logins.arn}"]
  }
}

result of terraform plan

  # module.cw_event_rules.data.aws_iam_policy_document.sns_topic_policy will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "sns_topic_policy"  {
      + id   = (known after apply)
      + json = (known after apply)

      + statement {
          + actions   = [
              + "SNS:Publish",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]

          + principals {
              + identifiers = [
                  + "events.amazonaws.com",
                ]
              + type        = "Service"
            }
        }
    }
 
  # module.cw_event_rules.aws_sns_topic.aws_logins will be created
  + resource "aws_sns_topic" "aws_logins" {
      + arn    = (known after apply)
      + id     = (known after apply)
      + name   = "aws-console-logins"
      + policy = (known after apply)
    }

  # module.cw_event_rules.aws_sns_topic_policy.default will be created
  + resource "aws_sns_topic_policy" "default" {
      + arn    = (known after apply)
      + id     = (known after apply)
      + policy = (known after apply)
    }

  # module.cw_event_rules.aws_cloudwatch_event_target.all_event_rules_map[0] will be created
  + resource "aws_cloudwatch_event_target" "all_event_rules_map" {
      + arn       = (known after apply)
      + id        = (known after apply)
      + rule      = "CIS-UnauthorizedActivityAttempt"
      + target_id = "SendToSNS"
    }
 
  # module.cw_event_rules.aws_cloudwatch_event_target.all_event_rules_map[1] will be created
  + resource "aws_cloudwatch_event_target" "all_event_rules_map" {
      + arn       = (known after apply)
      + id        = (known after apply)
      + rule      = "CIS-ConsoleLoginNoMFA"
      + target_id = "SendToSNS"
    }
 
 
 



No comments:

Post a Comment

AWS WAF log4j query

How to query AWS WAF log for log4j attacks 1. Setup your Athena table using this instruction https://docs.aws.amazon.com/athena/latest/ug/wa...