Managing AWS Certificate Manager (ACM) with Terraform

Learn how to provision and manage SSL/TLS certificates using AWS Certificate Manager and Terraform, including validation and integration with other AWS services

Managing AWS Certificate Manager (ACM) with Terraform

AWS Certificate Manager (ACM) provides SSL/TLS certificates for securing your applications. This guide demonstrates how to manage certificates using Terraform.

Architecture Overview

AWS Certificate Manager Architecture

The architecture diagram above shows:

  • Certificate Management: ACM for public certificates, Private CA for internal certificates, and Route 53 for DNS validation
  • Public Certificate Usage: Application Load Balancer and CloudFront distributing traffic to various compute services (ECS, EKS, Elastic Beanstalk)
  • Private Certificate Usage: Internal ALB with private certificates for internal services
  • User Traffic Flow: End users accessing applications through CloudFront and ALB with SSL/TLS encryption

Video Tutorial

Learn more about managing AWS Certificate Manager with Terraform in this comprehensive video tutorial:

View Source Code

Prerequisites

  • AWS CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Domain name registered in Route 53 (for DNS validation)
  • Basic understanding of SSL/TLS certificates

Project Structure

terraform-acm/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── acm/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── config/
    └── domains.json

ACM Configuration

Create modules/acm/main.tf:

# Public Certificate
resource "aws_acm_certificate" "main" {
  domain_name               = var.domain_name
  validation_method         = "DNS"
  subject_alternative_names = var.subject_alternative_names

  options {
    certificate_transparency_logging_preference = "ENABLED"
  }

  lifecycle {
    create_before_destroy = true
  }

  tags = merge(
    var.tags,
    {
      Name = var.domain_name
    }
  )
}

# DNS Validation Records
resource "aws_route53_record" "validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.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         = data.aws_route53_zone.main.zone_id
}

# Certificate Validation
resource "aws_acm_certificate_validation" "main" {
  certificate_arn         = aws_acm_certificate.main.arn
  validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
}

# Private Certificate Authority
resource "aws_acmpca_certificate_authority" "main" {
  certificate_authority_configuration {
    key_algorithm     = "RSA_4096"
    signing_algorithm = "SHA512WITHRSA"

    subject {
      common_name = var.domain_name
    }
  }

  permanent_deletion_time_in_days = 7
  type                           = "ROOT"

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-ca"
    }
  )
}

# Private Certificate
resource "aws_acm_certificate" "private" {
  domain_name               = "*.${var.domain_name}"
  certificate_authority_arn = aws_acmpca_certificate_authority.main.arn

  lifecycle {
    create_before_destroy = true
  }

  tags = merge(
    var.tags,
    {
      Name = "private-${var.domain_name}"
    }
  )
}

Integration with AWS Services

  1. Application Load Balancer
resource "aws_lb" "main" {
  name               = "${var.project_name}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = var.public_subnet_ids

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-alb"
    }
  )
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
  certificate_arn   = aws_acm_certificate.main.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.main.arn
  }
}
  1. CloudFront Distribution
resource "aws_cloudfront_distribution" "main" {
  enabled             = true
  is_ipv6_enabled     = true
  comment             = "Main distribution"
  default_root_object = "index.html"
  price_class         = "PriceClass_100"

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate.main.arn
    minimum_protocol_version = "TLSv1.2_2021"
    ssl_support_method      = "sni-only"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = local.s3_origin_id
    viewer_protocol_policy = "redirect-to-https"

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    min_ttl     = 0
    default_ttl = 3600
    max_ttl     = 86400
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-distribution"
    }
  )
}
  1. API Gateway
resource "aws_api_gateway_domain_name" "main" {
  domain_name              = "api.${var.domain_name}"
  regional_certificate_arn = aws_acm_certificate.main.arn

  endpoint_configuration {
    types = ["REGIONAL"]
  }

  tags = merge(
    var.tags,
    {
      Name = "api.${var.domain_name}"
    }
  )
}

resource "aws_route53_record" "api" {
  name    = aws_api_gateway_domain_name.main.domain_name
  type    = "A"
  zone_id = data.aws_route53_zone.main.zone_id

  alias {
    name                   = aws_api_gateway_domain_name.main.regional_domain_name
    zone_id                = aws_api_gateway_domain_name.main.regional_zone_id
    evaluate_target_health = true
  }
}

Certificate Renewal and Monitoring

  1. Certificate Expiry Monitoring
resource "aws_cloudwatch_metric_alarm" "certificate_expiry" {
  alarm_name          = "${var.project_name}-certificate-expiry"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "1"
  metric_name        = "DaysToExpiry"
  namespace          = "AWS/CertificateManager"
  period             = "86400"
  statistic          = "Minimum"
  threshold          = "30"
  alarm_description  = "Certificate will expire in less than 30 days"
  alarm_actions      = [aws_sns_topic.alerts.arn]

  dimensions = {
    CertificateArn = aws_acm_certificate.main.arn
  }
}
  1. SNS Topic for Alerts
resource "aws_sns_topic" "alerts" {
  name = "${var.project_name}-certificate-alerts"

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-alerts"
    }
  )
}

resource "aws_sns_topic_subscription" "email" {
  topic_arn = aws_sns_topic.alerts.arn
  protocol  = "email"
  endpoint  = var.alert_email
}

Advanced Features

  1. Importing Existing Certificates
resource "aws_acm_certificate" "imported" {
  private_key       = file("${path.module}/certs/private.key")
  certificate_body  = file("${path.module}/certs/certificate.crt")
  certificate_chain = file("${path.module}/certs/chain.crt")

  tags = merge(
    var.tags,
    {
      Name = "imported-${var.domain_name}"
    }
  )
}
  1. Cross-Region Certificate Usage
provider "aws" {
  alias  = "us-east-1"
  region = "us-east-1"
}

resource "aws_acm_certificate" "cloudfront" {
  provider                  = aws.us-east-1
  domain_name              = var.domain_name
  validation_method        = "DNS"
  subject_alternative_names = var.subject_alternative_names

  lifecycle {
    create_before_destroy = true
  }

  tags = merge(
    var.tags,
    {
      Name = "cloudfront-${var.domain_name}"
    }
  )
}

Best Practices

  1. Certificate Management

    • Use DNS validation
    • Enable certificate transparency
    • Monitor expiration
    • Implement auto-renewal
  2. Security

    • Use strong key algorithms
    • Enable logging
    • Control access
    • Monitor usage
  3. Cost Optimization

    • Monitor certificate usage
    • Clean up unused certificates
  4. Operational Excellence

    • Implement monitoring
    • Set up alerts
    • Document processes
    • Maintain inventory

Conclusion

You’ve learned how to set up and manage AWS Certificate Manager using Terraform. This setup provides:

  • SSL/TLS certificate provisioning
  • DNS validation
  • Service integration
  • Monitoring capabilities

Remember to:

  • Monitor certificate expiration
  • Implement proper security
  • Optimize costs
  • Maintain documentation