Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terraform with API-Gateway, Route53, and SSL Certification interdependency problem

I can't seem to get an SSL certificate from ACM working on API-Gateway, Route53, using terraform. There seems to be an interdependency problem.

data "aws_route53_zone" "root_domain" {
  name         = "${var.route53_root_domain_name}"
  private_zone = false
}

# The domain name to use with api-gateway
resource "aws_api_gateway_domain_name" "domain_name" {
  domain_name = "${var.route53_sub_domain_name}"

  certificate_arn = "${aws_acm_certificate.cert.arn}"
}

resource "aws_route53_record" "sub_domain" {
  name    = "${var.route53_sub_domain_name}"
  type    = "A"
  zone_id = "${data.aws_route53_zone.root_domain.zone_id}"

  alias {
    name                   = "${aws_api_gateway_domain_name.domain_name.cloudfront_domain_name}"
    zone_id                = "${aws_api_gateway_domain_name.domain_name.cloudfront_zone_id}"
    evaluate_target_health = false
  }
}

resource "aws_acm_certificate" "cert" {
  # api-gateway / cloudfront certificates need to use the us-east-1 region
  provider          = "aws.cloudfront-acm-certs"
  domain_name       = "${var.route53_sub_domain_name}"
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_route53_record" "cert_validation" {
  name    = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
  type    = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
  zone_id = "${aws_route53_record.sub_domain.zone_id}"
  records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
  ttl     = 60
}

resource "aws_acm_certificate_validation" "cert" {
  # api-gateway / cloudfront certificates need to use the us-east-1 region
  provider          = "aws.cloudfront-acm-certs"

  certificate_arn         = "${aws_acm_certificate.cert.arn}"
  validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]
}

The problem appears to be that:

  1. aws_api_gateway_domain_name requires aws_acm_certificate
  2. aws_acm_certificate has to be validated, so step 3
  3. aws_route53_record.cert_validation requires aws_route53_record.sub_domain
  4. aws_route53_record.subdomain requires aws_api_gateway_domain_name
  5. Go to 1

Everytime I try to use the configuration given, I get the following error:

aws_api_gateway_domain_name.domain_name: Error creating API Gateway Domain Name: BadRequestException: Unable to associate certificate arn:aws:acm:us-east-1:yyyy:certificate/zzzz with CloudFront. This error may prevent the domain name audit-log.taspli.com from being used in API Gateway for up to 40 minutes. Please ensure the certificate domain name matches the requested domain name, and that this user has permission to call cloudfront:UpdateDistribution on '*' resources. status code: 400, request id: xxxx

like image 665
Christopher Thomas Avatar asked Mar 06 '19 19:03

Christopher Thomas


Video Answer


2 Answers

I seem to have fixed the problem by adding the certificate validation records to the root domain instead of the sub domain. Therefore breaking the cyclic dependency.

The problem appears to be that the sub domain can't be created without the certificate and the certificate can't be validated without the sub domain. So the situation is stuck and unresolvable.

You could manually create the sub domain, but then whats the point in automation if you have to make manual efforts to solve problems.

So I tried adding the cert validation records to the root. Suddenly it starts to work, because the root domain is something that is created externally to the project. A sort of global infrastructure project which can be handled externally. Then your individual projects can hang off of that infrastructure on a case-by-case basis.

Here is the terraform configuration which worked:

data "aws_route53_zone" "root_domain" {
  name         = "${var.route53_root_domain_name}"
  private_zone = false
}

# The domain name to use with api-gateway
resource "aws_api_gateway_domain_name" "domain_name" {
  domain_name = "${var.route53_sub_domain_name}"

  certificate_arn = "${aws_acm_certificate.cert.arn}"
}

resource "aws_route53_record" "sub_domain" {
  name    = "${var.route53_sub_domain_name}"
  type    = "A"
  zone_id = "${data.aws_route53_zone.root_domain.zone_id}"

  alias {
    name                   = "${aws_api_gateway_domain_name.domain_name.cloudfront_domain_name}"
    zone_id                = "${aws_api_gateway_domain_name.domain_name.cloudfront_zone_id}"
    evaluate_target_health = false
  }
}

resource "aws_acm_certificate" "cert" {
  # api-gateway / cloudfront certificates need to use the us-east-1 region
  provider          = "aws.cloudfront-acm-certs"
  domain_name       = "${var.route53_sub_domain_name}"
  validation_method = "DNS"
}

resource "aws_route53_record" "cert_validation" {
  name    = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_name}"
  type    = "${aws_acm_certificate.cert.domain_validation_options.0.resource_record_type}"
  zone_id = "${data.aws_route53_zone.root_domain.zone_id}"
  records = ["${aws_acm_certificate.cert.domain_validation_options.0.resource_record_value}"]
  ttl     = 60
}

resource "aws_acm_certificate_validation" "cert" {
  # api-gateway / cloudfront certificates need to use the us-east-1 region
  provider          = "aws.cloudfront-acm-certs"

  certificate_arn         = "${aws_acm_certificate.cert.arn}"
  validation_record_fqdns = ["${aws_route53_record.cert_validation.fqdn}"]

  timeouts {
    create = "45m"
  }
}
like image 197
Christopher Thomas Avatar answered Oct 12 '22 11:10

Christopher Thomas


@Christopher Thomas thank you for your initial answer

With current terraform version it needs some modifications in the certificate blocks and I propose to set a provider in case your main region is not us-east-1. Since the changes are too long for a comment I add another answer for now

# api-gateway / cloudfront certificates need to use the us-east-1 region
provider "aws" {
  alias = "virginia"
  region = "us-east-1"
}

resource "aws_acm_certificate" "cert" {
  provider = aws.virginia
  domain_name       = local.api_subdomain
  validation_method = "DNS"
}

resource "aws_acm_certificate_validation" "cert" {
  provider = aws.virginia
  certificate_arn         = "${aws_acm_certificate.cert.arn}"
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}


resource "aws_route53_record" "cert_validation" {

  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }


  name            = each.value.name
  records         = [each.value.record]
  type            = each.value.type

  zone_id = data.aws_route53_zone.root_domain.zone_id
  ttl     = 60
}

resource "aws_acm_certificate_validation" "cert" {
  provider = aws.virginia
  certificate_arn         = "${aws_acm_certificate.cert.arn}"
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

# Finally Hook up Custom api domain name with api stage
resource "aws_api_gateway_base_path_mapping" "avxapi_base_path_mapping" {
  api_id      = aws_api_gateway_rest_api.xxxxxx.id
  stage_name  = aws_api_gateway_stage.xxxx.stage_name
  domain_name = aws_api_gateway_domain_name.xxxx.domain_name
}

like image 2
Dukeatcoding Avatar answered Oct 12 '22 10:10

Dukeatcoding