Like any good movie, there is always a plot twist, right?

Shortly after rolling out support for multiple domains was asked to redirect a few more secondary domains.

According to my last post, it should be easy, right?

Not so fast … I went ahead and added the new domains to the list

  alternative_domains = [
    {
        domain = "alternative.com"
        zone   = "alternative.com"
    },
    {   domain = "www.alternative.com"
        zone   = "alternative.com"
    },
    {   domain = "alternative.se"
        zone   = "alternative.se"
    },
    {   domain = "www.alternative.se"
        zone   = "alternative.se"
    },
    {   domain = "alternative.site"
        zone   = "alternative.site"
    },
    {   domain = "www.alternative.site"
        zone   = "alternative.site"
    }
  ]

Terraform apply … and … drum roll …

Error: Error modifying LB Listener Rule: ValidationError: A rule can only have '5' condition values
	status code: 400, request id: 19c18fa3-ed0d-4ca3-8b3e-d10b5221ac77
dooh!

here is the problem:

A Load balancer can have up to 100 Listener rules, and each listener rule can have up to 5 conditions.

Adding all alternative domains to the same listener rule exceeded that limit as soon as a few more were added to the list.

resource "aws_alb_listener_rule" "redirect_alternative_domains" {
  count = length(var.alternative_domains) > 0 ? 1 : 0
  listener_arn = aws_alb_listener.listener_443.arn
  priority     = 97
  action {
    type             = "redirect"
    target_group_arn = aws_alb_target_group.dashboard2_target_group.id
    redirect {
      host        = local.env_domain
      status_code = "HTTP_302"
    }
  }
  condition {
    host_header {
      values = [for ad in var.alternative_domains : ad.domain]
    }
  }
}

After a bit on the whiteboard, I could see 2 possible solutions.

The easy way out:

Just create one Listener rule per each forwarding condition. This means that www.domain.com would be one listener rule, and domain.com would be another rule until it reaches the 100 Listener rules limit.

The Optimal solution:

Create one rule per unique hosted zone, each rule containing the conditions for that specific zone.

This means each listener rule would contain a condition for both domain.com and www.domain.com

Rolled up my sleeves, created a new variable

distinct_ad_zones   = distinct([for d in var.alternative_domains : d.zone]) 

This one would serve as a condition to determine how many Listener rules I want to create

count = length(var.alternative_domains) > 0 ? length(local.distinct_ad_zones) : 0

And changed the header values to lookup at the map mapping

condition {
  host_header {
    values = [lookup(local.domain_to_zone_map, local.distinct_ad_zones[count.index])]
}

This is where things got hairy!

Being honnest, at this point, I cannot even remember if this was the first condition I tried or not. The point is, it was not working as expected!

My hopes were the lookup also worked the other way around but were only getting one of the values and not both of them, so I created another mapping:

zone_to_domain_map = zipmap(local.all_zones, local.all_domains)

leaving with two maps, one mapping domain to hosted zone, and another from hosted zone to the domain

{
domain.com      = domain.com,
www.domain.com  = domain.com,
domain.xyz      = domain.xyz,
www.domain.xyz  = domain.xyz,
domain.io       = domain.io,
www.domain.io   = domain.io
}

and

{
domain.com = domain.com,
domain.com = www.domain.com,
domain.xyz = domain.xyz,
domain.xyz = www.domain.xyz,
domain.io = domain.io,
domain.io = www.domain.io
}

I was also expecting the lookup function to return all the values for any given key with the same name, (local.distinct_ad_zones[count.index]) for example, for zone domain.com I would get both domains, the domain.com and the www.domain.com but that was not the case! I could only get either www.domain.com or domain.com in each listener rule or all of them in all listener rules. Never each pair per listener rule.

After some time back and forth, I noticed depending on the map I was looking up to, I was getting either one or the other, and a hackish solution just jumped up.

resource "aws_alb_listener_rule" "redirect_alternative_domains" {
  count = length(var.alternative_domains) > 0 ? length(local.distinct_ad_zones) : 0

  listener_arn = aws_alb_listener.listener_443.arn

  action {
    type             = "redirect"
    target_group_arn = aws_alb_target_group.dashboard2_target_group.id

    redirect {
      host        = local.env_domain
      status_code = "HTTP_302"
    }
  }

  condition {
    host_header {
      values = [
        lookup(local.domain_to_zone_map, local.distinct_ad_zones[count.index]),
        lookup(local.zone_to_domain_map, local.distinct_ad_zones[count.index])
      ]
    }
  }
}

Can you notice what I did there? yup … I simply lookup at both maps and I get the values I need where I need them!

I am sure this is not the best solution and someday, it will come back to haunt me! Let’s hope I don’t need to add a 3rd domain in the same zone! 🙈