Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DNS validation records are always created and destroyed when creating aws_acm_certificate with aws_route53_record #13317

Closed
johnhpatton opened this issue May 14, 2020 · 5 comments · Fixed by #11300
Assignees
Labels
bug Addresses a defect in current functionality. service/acm Issues and PRs that pertain to the acm service. service/route53 Issues and PRs that pertain to the route53 service.
Milestone

Comments

@johnhpatton
Copy link

johnhpatton commented May 14, 2020

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Terraform Version

$ terraform -v
Terraform v0.12.24

  • provider.aws v2.50.0
  • provider.local v1.2.2
  • provider.null v2.1.2

Affected Resource(s)

  • aws_acm_certificate with aws_acm_certificate_validation and aws_route53_record

Terraform Configuration Files

The following has been created as a module based on various examples on the internet and an attempt to use for_each to avoid a changing order triggering the resources to be tainted on every run. It didn't work. This does as much autodiscovery as I could possibly get it to do, but it still always creates and destroys validation records even when nothing has changed. This works similar to:

https://github.com/cloudposse/terraform-aws-acm-request-certificate

vars.tf

variable "create_certificate" {
  type        = bool
  default     = true
  description = "Set to false to prevent the module from creating or accessing any resources"
}

variable "zone_name" {
  type        = string
  default     = ""
  description = "The name of the desired Route53 Hosted Zone"
}

variable "domain_name" {
  type        = string
  description = "A domain name for which the certificate should be issued"
}

variable "subject_alternative_names" {
  type        = list(string)
  default     = []
  description = "A list of domains that should be SANs in the issued certificate"
}

variable "process_domain_validation_records" {
  type        = bool
  default     = true
  description = "Flag to enable/disable processing of the record to add to the DNS zone to complete certificate validation"
}

variable "wait_for_certificate_validation" {
  type        = bool
  default     = false
  description = "Flag to enable/disable waiting for validation of the certificate (the certificate status changed from `Pending Validation` to `Issued`)"
}

variable "validation_method" {
  type        = string
  default     = "DNS"
  description = "Method to use for validation, DNS or EMAIL"
}

variable "validation_record_ttl" {
  type        = string
  default     = "60"
  description = "The TTL of the record to add to the DNS zone to complete certificate validation"
}

variable "validation_allow_overwrite_records" {
  type        = bool
  default     = true
  description = "Whether or not to allow overwrite of Route53 validation records"
}

variable "acm_certificate_tags" {
  type        = map(string)
  default     = {}
  description = "Tags to attach to the ACM certificate"
}

main.tf

resource "aws_acm_certificate" "this" {
  count = var.create_certificate ? 1 : 0

  domain_name               = var.domain_name
  # using sort here to try to get anything to not trigger it to recreate
  subject_alternative_names = sort(var.subject_alternative_names)
  validation_method         = var.validation_method

  tags = var.acm_certificate_tags

  # Ignore changes on id, which is the date/time and forces validation on an unchanged cert, but this didn't seem to help.
  lifecycle {
    create_before_destroy = true
    ignore_changes = [id]
  }
}

resource "null_resource" "update_validation_records" {
  triggers = {
    validation_domains    = md5( join("", local.distinct_domain_names) )
  }
  depends_on = [ aws_acm_certificate.this[0] ]
}

resource "aws_route53_record" "validation" {
  # key = domain, value = zone
  for_each = local.zones_for_domain

  zone_id = each.value.zone_id

  name    = local.validation_records[each.key].resource_record_name
  type    = local.validation_records[each.key].resource_record_type
  ttl     = var.validation_record_ttl

  records = [
    local.validation_records[each.key].resource_record_value
  ]

  allow_overwrite = var.validation_allow_overwrite_records

  # try to tie this to a null_resource... still no luck.
  depends_on = [ null_resource.update_validation_records ]
}

resource "aws_acm_certificate_validation" "this" {
  count = local.process_domain_validation_records && var.wait_for_certificate_validation ? 1 : 0

  certificate_arn         = aws_acm_certificate.this[0].arn
  validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]

  # Ignore changes on id, but this didn't seem to help.
  lifecycle {
      ignore_changes = [ id ]
  }
}

locals.tf

data "aws_route53_zone" "public_zones" {
  for_each = toset(local.distinct_zone_names)
  name  = each.value
}

locals {
  // Get distinct list of domains and SANs
  distinct_domain_names = flatten(distinct(concat([var.domain_name], [for s in var.subject_alternative_names : replace(s, "*.", "")])))

  // Get distinct list of domains and SANs
  distinct_zone_names = flatten(distinct([for d in local.distinct_domain_names : "${regex("[^\\.]+\\.(.+)", d)[0]}."]))

  // Create set of distinct_domain_names -> aws_route53_zone
  zones_for_domain = { 
    for d in local.distinct_domain_names : d => data.aws_route53_zone.public_zones["${regex("[^\\.]+\\.(.+)", d)[0]}."]
  }

  # Get validation options mapped to distinct_domain_names to match zones_for_domain
  # Set:  domain_name => data.aws_rout53_zone associated with the domain
  validation_records = {
      for validation_option in aws_acm_certificate.this[0].domain_validation_options :
        replace(validation_option.domain_name, "*.", "") => validation_option if contains(local.distinct_domain_names, replace(validation_option.domain_name, "*.", ""))
    }

  // Boolean to determine if DNS validation records are required
  process_domain_validation_records = var.create_certificate && var.process_domain_validation_records && var.validation_method == "DNS"
}

Debug Output

Just running the terraform multiple times causes all validation records to always destroy and create, even when using for_each. I've tried everything... I don't know what debug output here can do to help.

Panic Output

N/A

Expected Behavior

The acm certificate and validation records should only be recreated and destroyed when they change.

Actual Behavior

The acm certification and validation records are recreated and destroyed on every run even when nothing has changed.

Steps to Reproduce

  1. terraform apply

Important Factoids

Nothing important to note.

References

@ghost ghost added service/acm Issues and PRs that pertain to the acm service. service/route53 Issues and PRs that pertain to the route53 service. labels May 14, 2020
@github-actions github-actions bot added the needs-triage Waiting for first response or review from a maintainer. label May 14, 2020
@johnhpatton
Copy link
Author

johnhpatton commented May 14, 2020

It looks like the subject_alternative_names setting is causing the aws_acm_certificate to be tainted even if the list is unchanged. I believe this is the primary issue.

  # module.cert.aws_acm_certificate.this[0] must be replaced
...
      ~ subject_alternative_names = [ # forces replacement

Secondary issue is related to unsorted validation records, which might be able to be worked around.

  # module.cert.aws_acm_certificate_validation.this[0] must be replaced
...
      ~ validation_record_fqdns = [
          - "XXX.myapp.example.com",
          - "YYY.myapp.example.com",
        ] -> (known after apply) # forces replacement

@rowlandm
Copy link

We had an issue where extra_aliases = ["atua.org.au", "www.atua.org.au"] kept getting the same kind of error.

When we changed it to extra_aliases = ["www.atua.org.au", "atua.org.au"] it worked.

@bflad bflad added bug Addresses a defect in current functionality. and removed needs-triage Waiting for first response or review from a maintainer. labels Jul 15, 2020
@bflad bflad self-assigned this Jul 15, 2020
@bflad bflad added this to the v3.0.0 milestone Jul 15, 2020
@bflad
Copy link
Contributor

bflad commented Jul 15, 2020

The fix to prevent ordering differences with the subject_alternative_names attribute in the aws_acm_certificate resource has been merged and will release with version 3.0.0 of the Terraform AWS Provider, likely in about two weeks.

@ghost
Copy link

ghost commented Jul 31, 2020

This has been released in version 3.0.0 of the Terraform AWS provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading.

For further feature requests or bug reports with this functionality, please create a new GitHub issue following the template for triage. Thanks!

@ghost
Copy link

ghost commented Aug 15, 2020

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 feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks!

@ghost ghost locked and limited conversation to collaborators Aug 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Addresses a defect in current functionality. service/acm Issues and PRs that pertain to the acm service. service/route53 Issues and PRs that pertain to the route53 service.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants