hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.82k stars 9.16k forks source link

Error when creating HTTPS LB listener using ACM issued certificate - UnsupportedCertificate #26245

Closed PowerShellPat closed 1 month ago

PowerShellPat commented 2 years ago

Community Note

Terraform CLI and Terraform AWS Provider Version

Terraform - 1.2.7 AWS provider - 4.25.0

Affected Resource(s)

Terraform Configuration Files

Please include all Terraform configurations required to reproduce the bug. Bug reports without a functional reproduction may be closed without investigation.

## Datasources
data "aws_vpc" "vpc" {
  filter {
    name   = "tag:Name"
    values = ["test-vpc"]
  }
}

data "aws_subnets" "public" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.vpc.id]
  }
  filter {
    name   = "tag:Name"
    values = ["*public-a", "*public-b"]
  }
}

data "aws_security_group" "alb_public" {
  filter {
    name   = "tag:Name"
    values = ["*alb-external-sg"]
  }
}

### Resources - Load balancer
resource "aws_lb" "test" {
  name               = "testing"
  load_balancer_type = "application"

  security_groups = [data.aws_security_group.alb_public.id]
  subnets         = data.aws_subnet.public.*.id
}

resource "aws_lb_target_group" "test" {
  name     = "testing"
  port     = 443
  protocol = "HTTPS"
  vpc_id   = data.aws_vpc.vpc.id
}

resource "aws_lb_listener" "test" {
  load_balancer_arn = aws_lb.test.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = aws_acm_certificate.public.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.test.arn
  }
}

### Resources - ACM + R53
resource "aws_acm_certificate" "public" {
  domain_name       = aws_route53_record.public.name
  validation_method = "DNS"
}

resource "aws_route53_record" "public" {
  zone_id = "XXX"
  name    = "name.XXX"
  type    = "A"

  alias {
    name                   = aws_lb.test.dns_name
    zone_id                = aws_lb.test.zone_id
    evaluate_target_health = false
  }
}

# R53 record for ACM validation for public web client
resource "aws_route53_record" "dvo_public" {
  for_each = {
    for dvo in aws_acm_certificate.public.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = "XXX"
}

resource "aws_acm_certificate_validation" "public" {
  certificate_arn         = aws_acm_certificate.public.arn
  validation_record_fqdns = [for record in aws_route53_record.dvo_public : record.fqdn]
}

Debug Output

Error: creating ELBv2 Listener (arn:aws:elasticloadbalancing:ap-southeast-2:XXX:loadbalancer/app/testing/XXX): UnsupportedCertificate: The certificate 'arn:aws:acm:ap-southeast-2:XXX:certificate/XXX' must have a fully-qualified domain name, a supported signature, and a supported key size. status code: 400, request id: XXX

Panic Output

N\A

Expected Behavior

The resource aws_acm_certificate_validation should be fully completed before moving onto the next resource aws_lb_listener.

A workaround is available by adding the depends_on block to the aws_lb_listener resource, dependant on the aws_acm_certificate_validation resource. This is how the console output below was produced:

aws_acm_certificate_validation.public_webclient: Creating...
aws_acm_certificate_validation.public_webclient: Creation complete after 1s [id=2022-08-11 11:31:59.691 +0000 UTC]
aws_lb_listener.test: Creating...
aws_lb_listener.test: Creation complete after 0s [id=arn:aws:elasticloadbalancing:ap-southeast-2:XXX:listener/app/testing/XXX/XXX]

It shows the graph being walked correctly. However, it may be that the certificate is not ready for consumption when the LB listener is being created.

Actual Behavior

Error as specified in the Debug Output section.

Steps to Reproduce

  1. terraform apply

Important Factoids

N\A

References

justinretzolk commented 2 years ago

Hey @PowerShellPat 👋 Thank you for taking the time to raise this! Am I correct in understanding that when you add the depends_on block to introduce the dependency on aws_acm_certificate_validation to aws_lb_listener, this works as expected?

PowerShellPat commented 2 years ago

Hi @justinretzolk

Correct; by adding the depends_on block, this code will run fine.

Having said this, it means we can't use the community module terraform-aws-alb without running the code twice if we have an ACM certificate being created and validated in the same deployment.

If we do add a depends_on block to the module to depend on aws_acm_certificate_validation, we run into cyclic errors.

justinretzolk commented 2 years ago

Hey @PowerShellPat 👋 Thank you for confirming that. I'm not sure I'm following on where you run into cycle errors when adding the depends_on block -- is it possible for you to supply a sample configuration illustrating that as well? I suspect that this is something that can be worked around by either a configuration change or a change to the module you linked to, and having that information may help me to provide that workaround.

PowerShellPat commented 2 years ago

Hi @justinretzolk,

Sorry for the delay in responding.

I've attached an example of when we run into cyclic errors when using the community module terraform-aws-alb:

module "alb_external" {
  source  = "terraform-aws-modules/alb/aws"
  version = "~> 6.0"

  name = "alb-testing"

  load_balancer_type = "application"

  vpc_id          = "vpc-XXX"
  subnets         = ["subnet-XXX", "subnet-XXX"]
  security_groups = ["sg-XXX"]
  target_groups = [ # We add the target to the TG as part of another deployment.
    {
      name             = "tg-test"
      backend_protocol = "HTTPS"
      backend_port     = 443
      target_type      = "instance"
    }
  ]

  https_listeners = [
    {
      port               = 443
      protocol           = "HTTPS"
      certificate_arn    = aws_acm_certificate.public.arn
      target_group_index = 0
    }
  ]

  tags = {
    name = "alb-testing"
  }

  depends_on = [
    aws_acm_certificate_validation.public
  ]
}

resource "aws_route53_record" "public_entry" {
  zone_id = "XXX"
  name    = "name.domain"
  type    = "A"

  alias {
    name                   = module.alb_external.lb_dns_name
    zone_id                = module.alb_external.lb_zone_id
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "public_acm" {
  for_each = {
    for dvo in aws_acm_certificate.public.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = "XXX"
}

resource "aws_acm_certificate" "public" {
  domain_name       = aws_route53_record.public_entry.name
  validation_method = "DNS"
}

resource "aws_acm_certificate_validation" "public" {
  certificate_arn         = aws_acm_certificate.public.arn
  validation_record_fqdns = [for record in aws_route53_record.public_acm : record.fqdn]
}

When running the code twice without the depends_on block in the module alb_external it works fine.

Here is the error we get when adding the depends_on block:


Error: Cycle: module.alb_external.output.lb_zone_id (expand), module.alb_external.var.name (expand), module.alb_external.var.load_balancer_update_timeout (expand), module.alb_external.var.drop_invalid_header_fields (expand), module.alb_external.var.name_prefix (expand), module.alb_external.var.desync_mitigation_mode (expand), module.alb_external.var.subnets (expand), module.alb_external.var.enable_http2 (expand), module.alb_external.var.access_logs (expand), module.alb_external.var.ip_address_type (expand), module.alb_external.var.load_balancer_type (expand), module.alb_external.var.idle_timeout (expand), module.alb_external.var.enable_waf_fail_open (expand), module.alb_external.var.putin_khuylo (expand), module.alb_external.local.create_lb (expand), module.alb_external.var.subnet_mapping (expand), module.alb_external.var.enable_cross_zone_load_balancing (expand), module.alb_external.var.enable_deletion_protection (expand), module.alb_external.var.internal (expand), module.alb_external.var.security_groups (expand), module.alb_external.var.load_balancer_create_timeout (expand), module.alb_external.var.load_balancer_delete_timeout (expand), module.alb_external.var.lb_tags (expand), module.alb_external.var.tags (expand), module.alb_external.aws_lb.this, module.alb_external.output.lb_dns_name (expand), aws_route53_record.public_entry, aws_acm_certificate.public, aws_route53_record.public_acm, aws_acm_certificate_validation.public, module.alb_external (expand), module.alb_external.var.create_lb (expand)
mattburgess commented 2 years ago

@PowerShellPat - I think you can break that dependency cycle by following the example at https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation#dns-validation-with-route-53. The trick here is to use the certifcate ARN that is held within the aws_certificate_validation resource so that you don't depend on both the cert and the validation thereof. In addition, and probably more critical than the former is to extract the FQDN of the cert into its own variable/local (or even just hardcoding it in both places would work but is a bit icky). That will break the cert -> dns record -> alb -> cert cycle you have. i.e. I think you want something like this (I've reordered your resources to be in dependency order, hopefully for clarity):

resource "aws_acm_certificate" "public" {
  domain_name       = var.alb_fqdn
  validation_method = "DNS"
}

resource "aws_route53_record" "public_acm" {
  for_each = {
    for dvo in aws_acm_certificate.public.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = "XXX"
}

resource "aws_acm_certificate_validation" "public" {
  certificate_arn         = aws_acm_certificate.public.arn
  validation_record_fqdns = [for record in aws_route53_record.public_acm : record.fqdn]
}

module "alb_external" {
  source  = "terraform-aws-modules/alb/aws"
  version = "~> 6.0"

  name = "alb-testing"

  load_balancer_type = "application"

  vpc_id          = "vpc-XXX"
  subnets         = ["subnet-XXX", "subnet-XXX"]
  security_groups = ["sg-XXX"]
  target_groups = [ # We add the target to the TG as part of another deployment.
    {
      name             = "tg-test"
      backend_protocol = "HTTPS"
      backend_port     = 443
      target_type      = "instance"
    }
  ]

  https_listeners = [
    {
      port               = 443
      protocol           = "HTTPS"
      certificate_arn    = aws_acm_certificate_validation.public.certificate_arn
      target_group_index = 0
    }
  ]

  tags = {
    name = "alb-testing"
  }
}

resource "aws_route53_record" "public_entry" {
  zone_id = "XXX"
  name    = var.alb_fqdn
  type    = "A"

  alias {
    name                   = module.alb_external.lb_dns_name
    zone_id                = module.alb_external.lb_zone_id
    evaluate_target_health = false
  }
}
github-actions[bot] commented 2 months ago

Marking this issue as stale due to inactivity. This helps our maintainers find and focus on the active issues. If this issue receives no comments in the next 30 days it will automatically be closed. Maintainers can also remove the stale label.

If this issue was automatically closed and you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thank you!

github-actions[bot] commented 2 days ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.