How to secure App Load Balancer with WAF and an ACM SSL Certificate via Terraform?

0

I have a .NET web application that is running via the application load balancer's DNS name that I use to access its website, wherein the network resources / topology, S3 buckets, the EC2 instance, and auto scaling group were all configured and created using Terraform. The ALB is internet-facing My primary dilemma is that the application is publicly accessible and isn't encrypted (http only for the website, but I want to encrypt it to turn it into HTTPS). I'm seeking to encrypt the web communication via SSL and apply authentication for it, along with a WAF for further protection. If I intend to secure the website with an WAF, SSL encryption, and utilize Terraform to create a self-signed certificate and save it in ACM, how might I exactly achieve this?

One idea that comes to mind is associating an ACM SSL certificate and a WAF with the ALB, but I'm not exactly sure how this could be done with using Terraform?

A possible consideration I did have for the code implementation includes the following:

# request public certificates from (ACM) 
resource "aws_acm_certificate" "acm_certificate" {
  provider = "aws.acm"
  domain_name               =             # Enter the domain name here
  subject_alternative_names = []     # Enter the alternate domain name here 
  validation_method         = "DNS"   # Authentication method

  lifecycle {
    create_before_destroy = true
  }
}

# get details about the route 53 hosted zone, but I have no route 53 domain name or hosted zone
data "aws_route53_zone" "route53_zone" {
  name         =      # Enter the domain name here
  private_zone = false
}

# create a record set in route 53 for domain validation, and to allow ACM to provide a CNAME and to add it to the record set?
resource "aws_route53_record" "route53_record" {
  for_each = {
    for dvo in aws_acm_certificate.acm_certificate.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.route53_zone.zone_id
}

# this will serve to validate acm certificate
resource "aws_acm_certificate_validation" "acm_certificate_validation" {
  certificate_arn         = aws_acm_certificate.acm_certificate.arn
  validation_record_fqdns = [for record in aws_route53_record.route53_record : record.fqdn]
}
  • please accept the answer if it was helpful

1 Answer
-1
  1. Request an ACM SSL Certificate
provider "aws" {
  region = "us-east-1" # Change to your desired region
}

resource "aws_acm_certificate" "acm_certificate" {
  domain_name               = "example.com" # Enter your domain name here
  subject_alternative_names = ["www.example.com"] # Enter any alternative domain names here
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

data "aws_route53_zone" "route53_zone" {
  name         = "example.com" # Enter your domain name here
  private_zone = false
}

resource "aws_route53_record" "route53_record" {
  for_each = {
    for dvo in aws_acm_certificate.acm_certificate.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.route53_zone.zone_id
}

resource "aws_acm_certificate_validation" "acm_certificate_validation" {
  certificate_arn         = aws_acm_certificate.acm_certificate.arn
  validation_record_fqdns = [for record in aws_route53_record.route53_record : record.fqdn]
}

  1. Set Up AWS WAF
resource "aws_wafv2_web_acl" "example" {
  name        = "example-waf"
  description = "WAF for ALB"
  scope       = "REGIONAL"
  default_action {
    allow {}
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "example-waf"
    sampled_requests_enabled   = true
  }

  rule {
    name     = "example-rule"
    priority = 1
    action {
      block {}
    }
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "example-rule"
      sampled_requests_enabled   = true
    }
  }
}

  1. Associate ACM Certificate and WAF with ALB
resource "aws_lb" "example" {
  name               = "example-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.example.id]
  subnets            = ["subnet-12345678", "subnet-87654321"] # Replace with your subnets

  enable_deletion_protection = false
}

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

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

resource "aws_lb_listener" "example_http" {
  load_balancer_arn = aws_lb.example.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "redirect"
    redirect {
      protocol = "HTTPS"
      port     = "443"
      status_code = "HTTP_301"
    }
  }
}

resource "aws_wafv2_web_acl_association" "example" {
  resource_arn = aws_lb.example.arn
  web_acl_arn  = aws_wafv2_web_acl.example.arn
}

  1. Configure Security Group

Make sure your security groups allow inbound traffic on port 80 (HTTP) and port 443 (HTTPS):

resource "aws_security_group" "example" {
  name_prefix = "example-alb-sg-"
  vpc_id      = "vpc-12345678" # Replace with your VPC ID

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Summary

This Terraform configuration sets up an Application Load Balancer with an ACM SSL certificate for HTTPS, redirects HTTP to HTTPS, and applies an AWS WAF Web ACL for additional security. Replace the placeholders with your actual values for domain names, subnets, VPC IDs, and any other specifics to your environment.

profile picture
EXPERT
answered 9 days ago
profile picture
EXPERT
reviewed 7 days ago
  • Oh, I did not anticipate such an in-depth Terraform configuration that involves the generation of the Application LB with the ACM SSL certificate and the AWS WAF WEB ACL! There is a matter that I am somewhat concerned about though. For the domain name that is associated with the request for the ACM SSL certificate and reference information from Route 53, if I don't have a domain name setup to use with the certificate request, is it possible to register and configure the custom domain name with Terraform as well? Or is there a different approach I would need to take with this dilemma?

  • If you want to use ALB with HTTPS listener, you will need a validated certificate anyway. For validation you will need a domain