AWS Networking with Terraform : Deploying a CloudFront with Primary and Failover Access Origin
Tags: aws, terraform, awscommunitybuilder, upskilling
Published: 2024-03-25 08:55:22

In our previous post, we integrated CloudFront distribution with website applications hosted on EC2 instances. Building upon that foundation, we now shift our focus to a critical aspect of cloud architecture: high availability origin failover. 

In this blog post, we'll see how CloudFront can be used to fortify the resilience of your infrastructure, employing Terraform to prepare the setup seamlessly.

## Architecture Overview:
Before diving into the implementation details, let's sketch out the architecture we'll be constructing:

![S3 Failover Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igjhw4ub1a7gljjn5lq3.png)

![CloudFront Origin Failover](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73gwtb1z6uwbmhg5zsx0.png)


## Step 1: Creating Primary and Failover S3 Buckets:
Firstly, we'll set up two distinct S3 buckets, each serving as a primary and failover origin respectively. These buckets will host the static website content.
```terraform
####################################################
# Create S3 Static Website
####################################################
module "s3_primary" {
  source        = "./modules/s3-static-website"
  bucket_name   = var.bucket_name_primary
  source_files  = "webfiles"
  common_tags   = local.common_tags
  naming_prefix = local.naming_prefix
}


module "s3_failover" {
  source        = "./modules/s3-static-website"
  bucket_name   = var.bucket_name_failover
  source_files  = "webfiles_failover"
  common_tags   = local.common_tags
  naming_prefix = local.naming_prefix
}
```

## Step 2: Configuring CloudFront Distribution:
Next, we'll craft a CloudFront distribution, integrating both the primary and failover S3 buckets as origins. This configuration helps CloudFront to dynamically switch to the failover origin in case of an outage or unavailability of the primary source.

```terraform
####################################################
# Create AWS Cloudfront distribution
####################################################
resource "aws_cloudfront_origin_access_control" "cf-s3-oac" {
  name                              = "CloudFront S3 OAC"
  description                       = "CloudFront S3 OAC"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_distribution" "cf-dist" {
  enabled             = true
  default_root_object = "index.html"

  origin_group {
    origin_id = "origin_group_id"
    failover_criteria {
      status_codes = [403, 404, 500, 502, 503, 504]
    }
    member {
      origin_id = "s3_primary"
    }
    member {
      origin_id = "s3_failover"
    }
  }

  origin {
    domain_name              = data.aws_s3_bucket.s3_primary.bucket_regional_domain_name
    origin_id                = "s3_primary"
    origin_access_control_id = aws_cloudfront_origin_access_control.cf-s3-oac.id
  }

  origin {
    domain_name              = data.aws_s3_bucket.s3_failover.bucket_regional_domain_name
    origin_id                = "s3_failover"
    origin_access_control_id = aws_cloudfront_origin_access_control.cf-s3-oac.id
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "origin_group_id"
    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }
    viewer_protocol_policy = "allow-all"
    min_ttl                = 0
    default_ttl            = 360
    max_ttl                = 86400
  }

  price_class = "PriceClass_All"

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["IN", "US", "CA"]
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  tags = merge(var.common_tags, {
    Name = "${var.naming_prefix}-cloudfront"
  })
}
```

## Step 3: Updating S3 Bucket Policies:
To facilitate seamless communication between CloudFront and the S3 buckets, we'll adjust the bucket policies accordingly.
```terraform
####################################################
# S3 bucket policy to allow access from cloudfront
####################################################
data "aws_iam_policy_document" "s3_bucket_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${var.bucket_arn}/*"]
    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceArn"
      values   = [var.cloudfront_distribution_arn]
    }
  }
}

resource "aws_s3_bucket_policy" "static_site_bucket_policy" {
  bucket = var.bucket_id
  policy = data.aws_iam_policy_document.s3_bucket_policy.json
}
```

## Steps to Run Terraform
Follow these steps to execute the Terraform configuration:
```terraform
terraform init
terraform plan 
terraform apply -auto-approve
```

Upon successful completion, Terraform will provide relevant outputs.
```terraform
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

cloudfront_domain_name = "http://d1rwkmekbjnbkd.cloudfront.net"
```

## Testing the outcome
S3 Primary and Failover Buckets

![S3 Bukcets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fed35jxy7rl1z7mq5bc5.png)

CloudFront Distribution:

![CF Distribution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3nsmr0nulcm2b573qvr.png)

CloudFront Distribution Origin as S3 primary and failover and origin group

![CF Origin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtr5bq2zb6t1wphxf0iy.png)


![CF Origin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2uoljd1ov9h51brq2ba9.png)

S3 Bucket Policy to allow access from cloudfront - primary and failover bucket

![S3 Bucket Policy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7t4rjwf0diurkguhiyu.png)

![S3 Bucket Policy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/232k2ecodx2inxjvdcrt.png)

Using cloudfront domain name to access S3 static website

![S3 Primary Origin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d7u5lygwu5tmzacxun65.png)

Failover to S3 failover bucket by removing s3 primary bucket policy manually from console (after TTL timeout)

![S3 Failover Origin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5841fabssiu5n7ur96f.png)

## Cleanup
Remember to stop AWS components to avoid large bills.
```terraform
terraform destroy -auto-approve
```

This how we can setup primary and failover access originas for CloudFront distribution for creating a robust high available architecture.

In our subsequent module, we'll embark on a journey into the realm of containerization with AWS, exploring how services like Amazon ECS can revolutionize application deployment and scalability.

## Resources:
GitHub Repo: https://github.com/chinmayto/terraform-aws-cloudfront-s3-failover
AWS CloudFront: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html
AWS CloudFront Origin Failover: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html