Create waf rule if environment is nonprod Terraform - amazon-web-services

I'm trying to create an IP whitelist in nonprod for load testing, the WAF is dynamically created in prod and nonprod based on the envname/envtype:
resource "aws_waf_ipset" "pwa_cloudfront_ip_restricted" {
name = "${var.envname}-pwa-cloudfront-whitelist"
dynamic "ip_set_descriptors" {
for_each = var.cloudfront_ip_restricted_waf_cidr_whitelist
content {
type = ip_set_descriptors.value.type
value = ip_set_descriptors.value.value
}
}
}
resource "aws_waf_rule" "pwa_cloudfront_ip_restricted" {
depends_on = [aws_waf_ipset.pwa_cloudfront_ip_restricted]
name = "${var.envname}-pwa-cloudfront-whitelist"
metric_name = "${var.envname}PWACloudfrontWhitelist"
predicates {
data_id = aws_waf_ipset.pwa_cloudfront_ip_restricted.id
negated = false
type = "IPMatch"
}
}
resource "aws_waf_ipset" "pwa_cloudfront_ip_restricted_load_testing" {
name = "${var.envname}-pwa-cloudfront-whitelist_load_testing"
count = var.envtype == "nonprod" ? 1 : 0
dynamic "ip_set_descriptors" {
for_each = var.cloudfront_ip_restricted_waf_cidr_whitelist_load_testing
content {
type = ip_set_descriptors.value.type
value = ip_set_descriptors.value.value
}
}
}
resource "aws_waf_rule" "pwa_cloudfront_ip_restricted_load_testing" {
depends_on = [aws_waf_ipset.pwa_cloudfront_ip_restricted_load_testing]
count = var.envtype == "nonprod" ? 1 : 0
name = "${var.envname}-pwa-cloudfront-whitelist-load_testing"
metric_name = "${var.envname}PWACloudfrontWhitelistload_testing"
predicates {
data_id = aws_waf_ipset.pwa_cloudfront_ip_restricted_load_testing[count.index].id
negated = false
type = "IPMatch"
}
}
resource "aws_waf_web_acl" "pwa_cloudfront_ip_restricted" {
name = "${var.envname}-pwa-cloudfront-whitelist"
metric_name = "${var.envname}PWACloudfrontWhitelist"
default_action {
type = "BLOCK"
}
rules {
action {
type = "ALLOW"
}
priority = 1
rule_id = aws_waf_rule.pwa_cloudfront_ip_restricted.id
type = "REGULAR"
}
rules {
action {
type = "ALLOW"
}
priority = 2
rule_id = aws_waf_rule.pwa_cloudfront_ip_restricted_load_testing.id
type = "REGULAR"
}
}
The second rules block throws and error in the terraform plan:
Error: Missing resource instance key
on waf.tf line 73, in resource "aws_waf_web_acl" "pwa_cloudfront_ip_restricted":
73: rule_id = aws_waf_rule.pwa_cloudfront_ip_restricted_load_testing.id
Because aws_waf_rule.pwa_cloudfront_ip_restricted_load_testing has "count" set,
its attributes must be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_waf_rule.pwa_cloudfront_ip_restricted_load_testing[count.index]
However if I add [count.index] :
Error: Reference to "count" in non-counted context
on waf.tf line 73, in resource "aws_waf_web_acl" "pwa_cloudfront_ip_restricted":
73: rule_id = aws_waf_rule.pwa_cloudfront_ip_restricted_load_testing[count.index].id
The "count" object can only be used in "module", "resource", and "data"
blocks, and only when the "count" argument is set.
Is there a way to do this that doesn't use the count param? Or am I missing something in the way that I am using it?

Since there is difference between the prod and non-prod environment, the way this should be tackled is by using dynamic [1] and for_each meta-argument [2]:
resource "aws_waf_web_acl" "pwa_cloudfront_ip_restricted" {
name = "${var.envname}-pwa-cloudfront-whitelist"
metric_name = "${var.envname}PWACloudfrontWhitelist"
default_action {
type = "BLOCK"
}
dynamic "rules" {
for_each = var.envtype == "nonprod" ? [1] : []
content {
action {
type = "ALLOW"
}
priority = 1
rule_id = aws_waf_rule.pwa_cloudfront_ip_restricted[0].id
type = "REGULAR"
}
}
dynamic "rules" {
for_each = var.envtype == "nonprod" ? [1] : []
content {
action {
type = "ALLOW"
}
priority = 2
rule_id = aws_waf_rule.pwa_cloudfront_ip_restricted_load_testing[0].id
type = "REGULAR"
}
}
}
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/expressions/for

Related

How to create iam-user module in terraform to cover 3 type of iam-user scenarios

Can you please help here on how to create iam-user module in terraform to cover 3 type of iam-user scenarios ?
PS: I don't want to create nested directory under modules/iam/iam-user/ to make each iam-user cases separately.
Following are the scenarios:
// Type 1
resource "aws_iam_user" "aws_iam_user_000" {
name = "user-000"
permissions_boundary = data.aws_iam_policy.permission_boundary.arn
}
resource "aws_iam_user_policy_attachment" "aws_iam_user_000" {
policy_arn = aws_iam_policy.s3_iam_policy.arn
user = aws_iam_user.aws_iam_user_000.name
}
// Type 2
resource "aws_iam_user" "aws_iam_user_001" {
path = "/"
for_each = toset(var.user_lists)
name = each.value
force_destroy = true
permissions_boundary = data.aws_iam_policy.permission_boundary.arn
}
resource "aws_iam_group" "aws_iam_group_001" {
name = "group-0001"
}
resource "aws_iam_user_group_membership" "group-membership" {
for_each = toset(var.user_lists)
user = aws_iam_user.aws_iam_user_001[each.value].name
groups = [aws_iam_group.aws_iam_group_001.name]
}
// Type 3
resource "aws_iam_user" "aws_iam_user_0002" {
name = "user-002"
tags = { "user_type" = "admin_account" }
permissions_boundary = data.aws_iam_policy.permission_boundary.arn
}
If I understand you correctly, you should be able to accomplish this using count and for_each with variables as below.
variables.tf
variable "is_admin" {
type = bool
default = false
}
variable "user_lists" {
type = list(any)
default = null
}
main.tf
// Type 1 and Type 3
resource "aws_iam_user" "this" {
count = var.user_lists == null ? 1 : 0
name = var.is_admin ? "user-000" : "user-002"
permissions_boundary = data.aws_iam_policy.permission_boundary.arn
tags = var.is_admin ? { "user_type" = "admin_account" } : null
}
resource "aws_iam_user_policy_attachment" "this" {
count = var.user_lists == null ? 1 : 0
policy_arn = aws_iam_policy.s3_iam_policy.arn
user = aws_iam_user.this[0].name
}
// Type 2
resource "aws_iam_user" "from_list" {
for_each = var.user_lists != null ? toset(var.user_lists) : []
path = "/"
name = each.value
force_destroy = true
permissions_boundary = data.aws_iam_policy.permission_boundary.arn
}
resource "aws_iam_group" "from_list" {
count = var.user_lists == null ? 1 : 0
name = "group-0001"
}
resource "aws_iam_user_group_membership" "this" {
for_each = var.user_lists != null ? toset(var.user_lists) : []
user = aws_iam_user.from_list[each.value].name
groups = [aws_iam_group.from_list[0].name]
}

terraform multiple aws route53 alias records under locals

I have a set of AliasRecords under terraform locals and wanted to map them under terraform's resource "aws_route53_record" . Below is the locals value:
locals {
AWSAliasRecordSets = [
{
Name = "api-dev.example.com.",
Type = "A",
AliasTarget = {
HostedZoneId = "EXAMPLE",
DNSName = "kjhskdjhf.cloudfront.net.",
EvaluateTargetHealth = false
}
},
{
Name = "api.example.com.",
Type = "A",
AliasTarget = {
HostedZoneId = "EXAMPLE",
DNSName = "jsdhgfjkdshf.cloudfront.net.",
EvaluateTargetHealth = false
}
}
]
}
What I am doing is :
locals {
FlatAWSAliasRecordSets = merge([
for idx, AWSAliasRecordSet in local.AWSAliasRecordSets:
{
for AliasTarget in AWSAliasRecordSet.AliasTarget:
"${idx}-${AliasTarget}" => {
HostedZoneId = AliasTarget["HostedZoneId"]
DNSName = AliasTarget["DNSName"]
EvaluateTargetHealth = AliasTarget["EvaluateTargetHealth"]
}
}
]...)
}
resource "aws_route53_record" "alias_records" {
for_each = local.FlatAWSAliasRecordSets
zone_id = each.value["HostedZoneId"]
name = each.value["AliasTarget"].Name
type = each.value["AliasTarget"].Type
alias {
zone_id = each.value["HostedZoneId"]
name = each.value["AliasTarget"].Name
evaluate_target_health = each.value["EvaluateTargetHealth"]
}
}
and when pushing to AWS ( terraform apply), it fails with below error:
│ on main.tf line 508, in locals:
│ 508: EvaluateTargetHealth = AliasTarget["EvaluateTargetHealth"]
│ This value does not have any indices.
Your AWSAliasRecordSets does not require flattening, as it is already flat. Thus you can go use regular count for it.
resource "aws_route53_record" "alias_records" {
count = length(local.AWSAliasRecordSets)
zone_id = local.AWSAliasRecordSets[count.index]["AliasTarget"].HostedZoneId
name = local.AWSAliasRecordSets[count.index].Name
type = local.AWSAliasRecordSets[count.index].Type
alias {
zone_id = local.AWSAliasRecordSets[count.index]["AliasTarget"].HostedZoneId
name = local.AWSAliasRecordSets[count.index].DNSName
evaluate_target_health = each.value["AliasTarget"].EvaluateTargetHealth
}
}
You also have to double check your use of Name and DNSName. Your current usage does not seem right to me, but this would be a new issue if this is really the case.

Wafv2 with terraform. how to do I exclude rules?

I created a WAFV2 as a modules and this is part of my code
name = var.name
description = "WAFv2 ACL for ${var.name}"
scope = var.scope
default_action {
allow {}
}
visibility_config {
cloudwatch_metrics_enabled = true
sampled_requests_enabled = true
metric_name = var.name
}
dynamic "rule" {
for_each = var.managed_rules
content {
name = rule.value.name
priority = rule.value.priority
override_action {
dynamic "none" {
for_each = rule.value.override_action == "none" ? [1] : []
content {}
}
dynamic "count" {
for_each = rule.value.override_action == "count" ? [1] : []
content {}
}
}
But after setting up kinesis firehouse I noticed some requests are been blocked by WAFV2, can anyone help me to figure out how to exclude some of the AwsManagdRules been blocked? here are some examples of them.
"CrossSiteScripting_BODY", "GenericLFI_BODY"
but I tried something like this one below in the tableau server using the waf is this correct?
name = aws_wafv2_rule_group.aws-wafv2-tableau.name
arn = aws_wafv2_rule_group.aws-wafv2-tableau.arn
priority = 0
override_action = "allow"
excluded_rules = ["CrossSiteScripting_BODY","GenericLFI_BODY"]
}]```

Terraform count within for_each loop

I'm trying to create GCP SQL DBs by iterating a list of string using Terraform's for_each and count parameter and the other loop is for the map keys (maindb & replicadb).
Unfortunately, I get the error that appears below.
Is it possible to do this is Terraform?
variables.tf
variable "sql_var" {
default = {
"maindb" = {
"db_list" = ["firstdb", "secondsdb", "thirddb"],
"disk_size" = "20",
},
"replicadb" = {
"db_list" = ["firstdb"],
"disk_size" = "",
}
}
}
main.tf
resource "google_sql_database_instance" "master_sql_instance" {
...
}
resource "google_sql_database" "database" {
for_each = var.sql_var
name = "${element(each.value.db_list, count.index)}"
instance = "${google_sql_database_instance.master_sql_instance[each.key].name}"
count = "${length(each.value.db_list)}"
}
Error Message
Error: Invalid combination of "count" and "for_each"
on ../main.tf line 43, in resource
"google_sql_database" "database": 43: for_each =
var.sql_var
The "count" and "for_each" meta-arguments are mutually-exclusive, only
one should be used to be explicit about the number of resources to be
created.
What the error message tells you is that you cannot use count and for_each together. It looks like you are trying to create 3 main databases and 1 replica database am I correct? What I would do is create your 2 master instances and then transform your map variable to create the databases.
terraform {
required_version = ">=0.13.3"
required_providers {
google = ">=3.36.0"
}
}
variable "sql_instances" {
default = {
"main_instance" = {
"db_list" = ["first_db", "second_db", "third_db"],
"disk_size" = "20",
},
"replica_instance" = {
"db_list" = ["first_db"],
"disk_size" = "20",
}
}
}
locals {
databases = flatten([
for key, value in var.sql_instances : [
for item in value.db_list : {
name = item
instance = key
}
]
])
sql_databases = {
for item in local.databases :
uuid() => item
}
}
resource "google_sql_database_instance" "sql_instance" {
for_each = var.sql_instances
name = each.key
settings {
disk_size = each.value.disk_size
tier = "db-f1-micro"
}
}
resource "google_sql_database" "sql_database" {
for_each = local.sql_databases
name = each.value.name
instance = each.value.instance
depends_on = [
google_sql_database_instance.sql_instance,
]
}
Then, first run terraform apply -target=google_sql_database_instance.sql_instance and after this run terraform apply.

How can I iterate through a map variable in terraform

Im trying to iterate through a variable type map and i'm not sure how to
This is what i have so far
In my main.tf:
resource "aws_route_53_record" "proxy_dns" {
count = "${length(var.account_name)}"
zone_id = "${infrastructure.zone_id}"
name = "proxy-${element(split(",", var.account_name), count.index)}-dns
type = CNAME
ttl = 60
records = ["{records.dns_name}"]
}
And in my variables.tf
variable "account_name" {
type = "map"
default = {
"account1" = "accountA"
"account2" = "accountB"
}
}
I want to be able to create multiple resources with the different account names
If you are using Terraform 0.12.6 or later then you can use for_each instead of count to produce one instance for each element in your map:
resource "aws_route53_record" "proxy_dns" {
for_each = var.account_name
zone_id = infrastructure.zone_id
name = "proxy-${each.value}-dns"
# ... etc ...
}
The primary advantage of for_each over count is that Terraform will identify the instances by the key in the map, so you'll get instances like aws_route53_record.proxy_dns["account1"] instead of aws_route53_record.proxy_dns[0], and so you can add and remove elements from your map in future with Terraform knowing which specific instance belongs to each element.
each.key and each.value in the resource type arguments replace count.index when for_each is used. They evaluate to the key and value of the current map element, respectively.
You can use a combination of map, keys function,index function, and count. This terraform creates 3 acls with various rules.
The names of the acl's are determined by the keys.
The number of acl's is determined by the count of the keys.
The index of each rule (priority) is determined by the index function
The name of each rule is from the CONTAINS_WORD or CONTAINS property in the map
=>
variable "acls" {
type = map(any)
default = {
"acl1" = {
"CONTAINS_WORD" = ["api","aaa", "bbb", "ccc"]
"CONTAINS" = ["xxx","yyy"]
}
"acl2" = {
"CONTAINS_WORD" = [ "url1,"url2","url3"]
"CONTAINS" = ["url4"]
}
"acl3" = {
"CONTAINS_WORD" = ["xxx"]
"CONTAINS" = []
}
}
}
resource "aws_wafv2_web_acl" "acl" {
name = keys(var.acls)[count.index]
scope = "REGIONAL"
count = length(keys(var.acls))
default_action {
block {}
}
dynamic "rule" {
for_each = toset(var.acls[keys(var.acls)[count.index]].CONTAINS_WORD)
content {
name = rule.key
priority = index(var.acls[keys(var.acls)[count.index]].CONTAINS_WORD, rule.key)
action {
allow {}
}
statement {
#https://docs.aws.amazon.com/waf/latest/APIReference/API_ByteMatchStatement.html
byte_match_statement {
positional_constraint = "CONTAINS_WORD"
search_string = lower(rule.key)
field_to_match {
uri_path {}
}
text_transformation {
priority = 0
type = "LOWERCASE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "waf-${keys(var.acls)[count.index]}-${rule.key}"
sampled_requests_enabled = true
}
}
}
dynamic "rule" {
for_each = toset(var.acls[keys(var.acls)[count.index]].CONTAINS)
content {
name = replace(rule.key, ".", "_")
priority = index(var.acls[keys(var.acls)[count.index]].CONTAINS, rule.key) + length(var.acls[keys(var.acls)[count.index]].CONTAINS_WORD)
action {
allow {}
}
statement {
#https://docs.aws.amazon.com/waf/latest/APIReference/API_ByteMatchStatement.html
byte_match_statement {
positional_constraint = "CONTAINS"
search_string = lower(rule.key)
field_to_match {
uri_path {}
}
text_transformation {
priority = 0
type = "LOWERCASE"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "waf-${keys(var.acls)[count.index]}-${replace(rule.key, ".", "_")}"
sampled_requests_enabled = true
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "waf-${keys(var.acls)[count.index]}"
sampled_requests_enabled = true
}
}
Make the variable a list instead of a map. Maps are used to reference a name to a value. Lists are better for iterating over via a count method.
variable "account_name" {
type = "list"
default = {"accountA","accountB"}
}
resource "aws_route_53_record" "proxy_dns" {
count = "${length(var.account_name)}"
zone_id = "${infrastructure.zone_id}"
name = "proxy-${element(var.account_name, count.index)}-dns
type = CNAME
ttl = 60
records = ["{records.dns_name}"]
}