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
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:
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
- 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
}
}
- 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"
}
)
}
- 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
- 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
}
}
- 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
- 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}"
}
)
}
- 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
-
Certificate Management
- Use DNS validation
- Enable certificate transparency
- Monitor expiration
- Implement auto-renewal
-
Security
- Use strong key algorithms
- Enable logging
- Control access
- Monitor usage
-
Cost Optimization
- Monitor certificate usage
- Clean up unused certificates
-
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