I'm trying to add an AWS WAF rule that grabs the values from variables and adds it as a block definition into the resource. I'm not sure if what I'm trying is possible or not, I'm getting the error:
An argument or block definition is required here. To set an argument, use the equals sign "=" to introduce the argument value.
I'm guessing it's because it's seeing the variable in quotes and expecting ito to have an argument.
The block in question below is the "field_to_match" block.
Is there any way this can be done or an alternative option?
resource "aws_wafv2_web_acl" "static_hosting" {
provider = aws.acm_us_region
name = "${var.cityName}-static-hosting"
description = "WAF for ${var.cityName}-static-hosting"
scope = "CLOUDFRONT"
default_action {
block {}
}
rule {
name = "Check_Domain"
priority = 1
action {
allow {}
}
statement {
byte_match_statement {
text_transformation {
priority = 0
type = "NONE"
}
field_to_match {
var.serviceConfig.static_hosting.conditionalType.type {
name = var.serviceConfig.static_hosting.conditionalType.name
}
}
positional_constraint = var.serviceConfig.static_hosting.conditionalType.conditional
search_string = var.serviceConfig.static_hosting.conditionalType.string
}
}
}
"conditionalType": {
"type": "single_header",
"name": "referrer",
"conditional": "CONTAINS",
"string": "domain.com"
}
The arguments and blocks within a resource configuration cannot be dynamic in this way in Terraform, because they are checked statically at validation time rather than dynamically during plan/apply.
It seems like your goal here is to dynamically choose which of the several different possible block types to use inside byte_match_statement
, based on the value of var.serviceConfig.static_hosting.conditionalType.type
. Due to the design of this resource type this isn't a straightforward thing to write, but it is possible to achieve it by using a few different kinds of expression along with some dynamic
blocks.
I'd start here by first producing some values that more directly match the expectations of dynamic
blocks, where we have a collection to iterate over per block rather than a single name lookup. For example:
locals {
query_type = var.serviceConfig.static_hosting.conditionalType.type
all_query_arguments = local.query_type == "all_query_arguments" ? {} : null
body = local.query_type == "body" ? {} : null
method = local.query_type == "method" ? {} : null
query_string = local.query_type == "query_string" ? {} : null
uri_path = local.query_type == "uri_path" ? {} : null
single_header = local.query_type == "single_header" ? {
name = var.serviceConfig.static_hosting.conditionalType.name
} : null
single_query_argument = local.query_type == "single_query_argument" ? {
name = var.serviceConfig.static_hosting.conditionalType.name
} : null
}
What we've achieved here is having a separate symbol for each of the possible query types where at any time only one of them will be set and the others will all be null
. This isn't quite what a dynamic
block expects, but we can easily adapt from a value that might be null into a list with either zero or one elements using the splat operator, [*]
. For example, local.body[*]
would either be an empty list or a list containing a single element, depending on whether local.body
is null
.
This then gives us the inputs we need to write dynamic
blocks that will produce either zero or one of each of the allowed block types, which means that in practice there will only be one total because we've arranged above that only one of them can be non-null
at a time.
field_to_match {
dynamic "all_query_arguments" {
for_each = local.all_query_arguments[*]
content {}
}
dynamic "body" {
for_each = local.body[*]
content {}
}
dynamic "method" {
for_each = local.method[*]
content {}
}
dynamic "query_string" {
for_each = local.query_string[*]
content {}
}
dynamic "uri_path" {
for_each = local.uri_path[*]
content {}
}
dynamic "single_header" {
for_each = local.uri_single_header[*]
content {
name = single_header.value.name
}
}
dynamic "single_query_argument" {
for_each = local.single_query_argument[*]
content {
name = single_query_argument.value.name
}
}
}
This design of having a variety of different block types from which you can pick any one to set a value is a somewhat unusual design for a Terraform resource type, and so unfortunately making use of it dynamically like this is rather involved compared to the equivalent static configuration, but it should still be possible to make it dynamic with a strategy like this, albeit rather verbosely in order to allow Terraform to see that all of the possible outcomes will conform to the resource type's declared schema.