Thursday, January 30, 2020

Terraform working with Collection Functions

Terraform Notes

Working with Collection Functions


Here's a little challenge: Gather the structure of existing OU of AWS Organization. We are not going to get all the OU IDs and import then into our Terraform State. We are also not going to just [destroy and] rebuild them. Instead we are going to get it using Data call.

One hurdle was that each call to retrieve OU only returns its immediate child.
https://www.terraform.io/docs/providers/aws/d/organizations_organizational_units.html

So I had to hardcode how deep I wanted to retrieve (AWS limits you to 5 deep for OU). But this is a good illustration of working with Map, List, flatten, and concat.

Few highlights:


When you do a data call as a count loop, Terraform will create a list of 3 attributes
  • Terraform ID
  • Attribute used as an argument reference (in the case of Org, this is Parent_ID)
  • List of the expected attribute reference (in the case of Org, this is Children)
When you flatten a list of attributes that contains another list of attributes, your end result is list of attribute. This is how I picture it:

Finding a attribute's value in a list and then returning another attribute's value from that index is pretty straight forward:

parent_name = [for x in local.level1_ou_flat: x.name if x.id == ou.parent_id][0]

This code says, loop through all the items in local.level1_ou_flat and only keep name where id is  ou.parent_id. The above code snippet works because I know that all the names returned will be the same, so I just keep the first item in the list. 

When you output something from module, this will be returned to main call, so from main you have to explicitly output this value which can be referenced using the module's name that you defined in the main. No, you don't have to call this output, main. Also, output is ONLY invoked when you do Terraform Apply. Terraform Plan, unfortunately does not provide output.

In Module:

output "ou_tree" {
  value = local.all_ou
}

In Main:

output "main"{
  value = module.org_scp.ou_tree
}

main.tf
provider "aws" {
  alias    = "master_east_2"
  version = "~> 2.0"
  region  = "us-east-2"
  access_key = "AAAAAAAAAAAAAAAAAAA"
  secret_key = "zzzzzzzzzzzzzzzzzzz"
}

module "org_scp" {
  source = "./module"
  providers = {
    aws = aws.master_east_2
  }  
}

output "main"{
  value = module.org_scp.ou_tree
}

module/m_org_scp.tf

// this return information about org: https://www.terraform.io/docs/providers/aws/d/organizations_organization.html
data "aws_organizations_organization" "myRoot"{
}

/*
data.aws_organizations_organization.myRoot.roots[0].id
data.aws_organizations_organization.myRoot.roots[0].name
data.aws_organizations_organization.myRoot.roots[0].arn
*/

// root will only have 1 element, but we leave this open-ended for consistency with the remaining levels
data "aws_organizations_organizational_units" "level1_ou"{
    count = length(data.aws_organizations_organization.myRoot.roots)
    parent_id = data.aws_organizations_organization.myRoot.roots[count.index].id
}

/* 
data.aws_organizations_organizational_units.level1_ou[*].parent_id
data.aws_organizations_organizational_units.level1_ou[*].id  <-- don't use this
data.aws_organizations_organizational_units.level1_ou[*].children[*].id
data.aws_organizations_organizational_units.level1_ou[*].children[*].arn
data.aws_organizations_organizational_units.level1_ou[*].children[*].name 
*/

locals {
    level1_ou_flat = flatten([
        for ou in data.aws_organizations_organizational_units.level1_ou : [
            for child in ou.children : {
                parent_id = ou.parent_id
                parent_name = data.aws_organizations_organization.myRoot.roots[0].name
                id = child.id
                name = child.name
                arn = child.arn
                path = "${data.aws_organizations_organization.myRoot.roots[0].name}/${child.name}"
            }
        ]
    ])        
}

/*
local.level1_ou_flat[*].parent_id
local.level1_ou_flat[*].parent_name
local.level1_ou_flat[*].id
local.level1_ou_flat[*].name
local.level1_ou_flat[*].arn
local.level1_ou_flat[*].path
*/

// this returns a list of list of OUs on layer 2
data "aws_organizations_organizational_units" "level2_ou"{
    count = length(local.level1_ou_flat)
    parent_id = local.level1_ou_flat[count.index].id 
}

/* 
data.aws_organizations_organizational_units.level2_ou[*].parent_id
data.aws_organizations_organizational_units.level2_ou[*].id  <-- don't use this
data.aws_organizations_organizational_units.level2_ou[*].children[*].id
data.aws_organizations_organizational_units.level2_ou[*].children[*].arn
data.aws_organizations_organizational_units.level2_ou[*].children[*].name 
*/

// flatten the list of list layer 2
locals {
    level2_ou_flat = flatten([
        for ou in data.aws_organizations_organizational_units.level2_ou : [
            for child in ou.children : {
                parent_id = ou.parent_id
                parent_name = [for x in local.level1_ou_flat: x.name if x.id == ou.parent_id][0]
                id = child.id
                name = child.name
                arn = child.arn
                path = "${[for x in local.level1_ou_flat: x.path if x.id == ou.parent_id][0]}/${child.name}"
            }
        ]
    ])        
}

/*
local.level2_ou_flat[*].parent_id
local.level2_ou_flat[*].parent_name
local.level2_ou_flat[*].id
local.level2_ou_flat[*].name
local.level2_ou_flat[*].arn
local.level2_ou_flat[*].path
*/

// this returns a list of list of OUs on layer 3
data "aws_organizations_organizational_units" "level3_ou"{
    count = length(local.level2_ou_flat)
    parent_id = local.level2_ou_flat[count.index].id
}

// flatten the list of list layer 3
locals {
    level3_ou_flat = flatten([
        for ou in data.aws_organizations_organizational_units.level3_ou : [
            for child in ou.children : {
                parent_id = ou.parent_id
                parent_name = [for x in local.level2_ou_flat: x.name if x.id == ou.parent_id][0]
                id = child.id
                name = child.name
                arn = child.arn
                path = "${[for x in local.level2_ou_flat: x.path if x.id == ou.parent_id][0]}/${child.name}"
            }
        ]
    ])        
}

// this returns a list of list of OUs on layer 4
data "aws_organizations_organizational_units" "level4_ou"{
    count = length(local.level3_ou_flat)
    parent_id = local.level3_ou_flat[count.index].id
}

// flatten the list of list layer 4
locals {
    level4_ou_flat = flatten([
        for ou in data.aws_organizations_organizational_units.level4_ou : [
            for child in ou.children : {
                parent_id = ou.parent_id
                parent_name = [for x in local.level3_ou_flat: x.name if x.id == ou.parent_id][0]
                id = child.id
                name = child.name
                arn = child.arn
                path = "${[for x in local.level3_ou_flat: x.path if x.id == ou.parent_id][0]}/${child.name}"
            }
        ]
    ])        
}

// this returns a list of list of OUs on layer 5
data "aws_organizations_organizational_units" "level5_ou"{
    count = length(local.level4_ou_flat)
    parent_id = local.level4_ou_flat[count.index].id
}

// flatten the list of list layer 5
locals {
    level5_ou_flat = flatten([
        for ou in data.aws_organizations_organizational_units.level5_ou : [
            for child in ou.children : {
                parent_id = ou.parent_id
                parent_name = [for x in local.level4_ou_flat: x.name if x.id == ou.parent_id][0]
                id = child.id
                name = child.name
                arn = child.arn
                path = "${[for x in local.level4_ou_flat: x.path if x.id == ou.parent_id][0]}/${child.name}"
            }
        ]
    ])        
}


locals {
    // AWS Organization currently only supports 1 root
    root_id = data.aws_organizations_organization.myRoot.roots[0].id
    root_accounts = data.aws_organizations_organization.myRoot.accounts[*].id
    all_ou = concat(local.level1_ou_flat,local.level2_ou_flat,local.level3_ou_flat,local.level4_ou_flat,local.level5_ou_flat)

    /*
    local.all_ou[*].parent_id
    local.all_ou[*].parent_name
    local.all_ou[*].id
    local.all_ou[*].name
    local.all_ou[*].arn
    local.all_ou[*].path
    */
}


output "ou_tree" {
  value = local.all_ou
}

output:

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

main = [
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-wdbh6dlt"
    "id" = "ou-abcd-wdbh6dlt"
    "name" = "QA"
    "parent_id" = "r-abcd"
    "parent_name" = "Root"
    "path" = "Root/QA"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-zuidgyzl"
    "id" = "ou-abcd-zuidgyzl"
    "name" = "Test"
    "parent_id" = "r-abcd"
    "parent_name" = "Root"
    "path" = "Root/Test"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-dpde5cp9"
    "id" = "ou-abcd-dpde5cp9"
    "name" = "Prod"
    "parent_id" = "r-abcd"
    "parent_name" = "Root"
    "path" = "Root/Prod"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-bi34zvmq"
    "id" = "ou-abcd-bi34zvmq"
    "name" = "QA_App"
    "parent_id" = "ou-abcd-wdbh6dlt"
    "parent_name" = "QA"
    "path" = "Root/QA/QA_App"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-fbj0cnnr"
    "id" = "ou-abcd-fbj0cnnr"
    "name" = "App1"
    "parent_id" = "ou-abcd-zuidgyzl"
    "parent_name" = "Test"
    "path" = "Root/Test/App1"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-t3ngdv40"
    "id" = "ou-abcd-t3ngdv40"
    "name" = "Prod_App2"
    "parent_id" = "ou-abcd-dpde5cp9"
    "parent_name" = "Prod"
    "path" = "Root/Prod/Prod_App2"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-qubfzrrr"
    "id" = "ou-abcd-qubfzrrr"
    "name" = "Prod_App"
    "parent_id" = "ou-abcd-dpde5cp9"
    "parent_name" = "Prod"
    "path" = "Root/Prod/Prod_App"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-x4csr627"
    "id" = "ou-abcd-x4csr627"
    "name" = "SubDeptA"
    "parent_id" = "ou-abcd-fbj0cnnr"
    "parent_name" = "App1"
    "path" = "Root/Test/App1/SubDeptA"
  },
  {
    "arn" = "arn:aws:organizations::111111111111:ou/o-fyteu3rxnm/ou-abcd-cii1t2vv"
    "id" = "ou-abcd-cii1t2vv"
    "name" = "SubDeptB"
    "parent_id" = "ou-abcd-fbj0cnnr"
    "parent_name" = "App1"
    "path" = "Root/Test/App1/SubDeptB"
  },
]





Tuesday, January 28, 2020

Terraform working with variables

Terraform Notes

Working with Variables

In this example, I am populating a dummy Service Control Policy (SCP) in my Org. As such, I need to provide target ID that is the root's ID.

main.tf
provider "aws" {
  alias    = "master_east_2"
  version = "~> 2.0"
  region  = "us-east-2"
  access_key = "AAAAAAAAAAAAAAAAA"
  secret_key = "ffffffffffffffffffffffffffffffffff"
}

variable "main_target_id" {
    type = string
    default = "r-ppg1"
}

module "org_scp" {
  source = "./module"
  module_target_id = var.main_target_id
  providers = {
    aws = aws.master_east_2
  }  
}

From main.tf, I feed in a local variable called main_target_id with the value "r-ppg1" this is the id of the org root. The module is using a variable called "module_target_id," we define this in the module file in the variable block. The variable block is used to define the type and default, if applicable.


module/module_scp.tf

resource "aws_organizations_policy" "example" {
  name = "example"

  content = <<CONTENT
{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }
}
CONTENT
}

variable "module_target_id" {
    type = string
}

resource "aws_organizations_policy_attachment" "root" {
  policy_id = aws_organizations_policy.example.id
  target_id = var.module_target_id
}

The resource "aws_organizations_policy_attachment" is expecting two arguments policy_id and target_id. These we don't control.

Thursday, January 16, 2020

Terraform loop - count vs for_each

Terraform Notes

Using Count Loop vs For_Each Loop

In my previous example, Terraform Cloudwatch Event Rules, I used Count to loop through a List of CloudWatch rules. One downside of doing it this way is that if you have to remove 1 resource in the array, it causes a cascade of delete and recreates to ensure the indices are reordered.
So if you have 5 resources and you delete [2], following will occur.

[1]   -->   [1]
[2]   -->    X
[3]   -->    X    -->   [2]
[4]   -->    X    -->   [3]
[5]   -->    X    -->   [4]

The better way to do this (supported as of Terraform version 0.12) is to use for_each. Here is my example.

This is the my variable
variable "event_rules" {
    description = "Create event rules with following name, description, and rule JSON"
    type = map(map(string))
    default = {
        "CIS3_1" = {
            "name"="CIS-UnauthorizedActivityAttempt",
            "description"="3.1 UnauthorizedActivityAttempt",
            "filename"="CIS-UnauthorizedActivityAttempt.json"
        },
        "CIS3_2" = {
            "name"="CIS-ConsoleLoginNoMFA",
            "description"="3.2 ConsoleLoginNoMFA",
            "filename"="CIS-ConsoleLoginNoMFA.json"
        }
    }
}

This is where resources are created

resource "aws_cloudwatch_event_rule" "all_events_map" {
    for_each = var.event_rules_map
      name = each.value["name"]
      description = each.value["description"]
      event_pattern = file("${path.module}/jsons/${each.value["filename"]}")
}

Here is another example, where we nest the for loop so that we evaluate the aws_cloudwatch_event_rule before we take action on it. The inner for loop will create a map that uses key as the key and value as its value (key => value) only if the following if statement resolves to true. Then the outer for loop will run against that resulting map. So it's possible that no resource gets created.


resource "aws_lambda_permission" "all_events_map" {
  for_each = {
    for key, value in aws_cloudwatch_event_rule.all_events_map:
    key => value
    if length(var.lambda_function_name) > 0
  }
    statement_id  = each.value["name"]
    action        = "lambda:InvokeFunction"
    function_name = var.lambda_function_name
    principal     = "events.amazonaws.com"
    source_arn    = each.value["arn"]
}








Terraform AWS Assume Role


Using AWS Assume Role in Terraform

You built a Terraform build server or TFE server on AWS EC2.  You want to be able to deploy resources to multiple accounts in your organization. How do you do this?

References:
https://www.terraform.io/docs/providers/aws/index.html
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#w329aac23c19c25c21b9b3
https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_permissions-to-switch.html
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html


Be sure you have added an Instance Profile loaded onto your instance, you can extract the temporary credential for testing.

This is the aws cli command to look up your own identity:

aws sts get-caller-identity


This is how you extract the temp cred :
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/SOURCE_ROLE_NAME

Let's say from this instance you want to execute Terraform commands against a different role than the Instance Profile.

Be sure the Source Role is given necessary permission to assume the Target Role

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::888888888888:role/TARGET_ROLE_NAME"
            
        }
    ]
}

And the Target Role must have the necessary Trust relationship to the Source Role. In the below example, I also limit this action to happen from some known IP range.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999999999999:role/SOURCE_ROLE_NAME"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "10.10.10.0/24"
        }
      }
    }
  ]
}

You can test your policy via this command
aws sts assume-role --role-arn arn:aws:iam::888888888888:role/TARGET_ROLE_NAME --role-session-name MySession

Once your test works, then you can use the assume_role block inside "aws" provider.

Assume Role Usage:

provider "aws" {
  assume_role {
    role_arn     = "arn:aws:iam::88888888888/TARGET_ROLE_NAME"
    session_name = "MY_SESSION_NAME"
    external_id  = "SOME_OPTIONAL_EXTERNAL_ID"
  }
}

Thursday, January 9, 2020

Terraform Useful Powershell Aliases

Terraform Notes

Useful Powershell Aliases

tplan: terraform plan with paginate with compact warning

function fun_tplan {terraform plan -no-color -compact-warnings | more}
set-alias -name tplan -value fun_tplan

tapply: terraform apply with paginate, compact warning, and automate accept


function fun_tapply {terraform apply -auto-approve -no-color -compact-warnings | more}
set-alias -name tapply -value fun_tapply


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"
    }
 
 
 



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...