Skip to content

Commit

Permalink
feat(terraform): add CDN infrastructure (part 1) (#80)
Browse files Browse the repository at this point in the history
* feat(terraform): add CDN infrastructure

* feat(terraform): add `assets_version`

* fix(terraform): add `assets_version` to service module call

* fix(terraform): tweak comment

* fix(terraform): fixes
  • Loading branch information
JoshuaLicense authored Apr 24, 2024
1 parent ae624a2 commit 74a4dff
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy-environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ jobs:
TF_VAR_api_image_tag: ${{ inputs.api-image-tag }}
TF_VAR_selfserve_image_tag: ${{ inputs.selfserve-image-tag }}
TF_VAR_internal_image_tag: ${{ inputs.internal-image-tag }}
TF_VAR_assets_version: test # TODO
run: terraform plan ${{ inputs.destroy && '-destroy ' || '' }} -no-color -input=false -out=tfplan ${{ inputs.terraform-args || '' }} && terraform show -no-color tfplan

- name: Get plan changes
Expand Down Expand Up @@ -230,6 +231,7 @@ jobs:
TF_VAR_api_image_tag: ${{ inputs.api-image-tag }}
TF_VAR_selfserve_image_tag: ${{ inputs.selfserve-image-tag }}
TF_VAR_internal_image_tag: ${{ inputs.internal-image-tag }}
TF_VAR_assets_version: test # TODO
run: terraform apply ${{ inputs.destroy && '-destroy ' || '' }} -no-color -input=false -auto-approve ${{ inputs.terraform-args || '' }}

- name: Delete workspace
Expand Down
3 changes: 3 additions & 0 deletions infra/terraform/environments/dev/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ module "service" {

environment = "dev"

domain_name = "dev.olcs.dev-dvsacloud.uk"
assets_version = var.assets_version

services = {
"api" = {
cpu = 1024
Expand Down
5 changes: 5 additions & 0 deletions infra/terraform/environments/dev/variables.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
variable "assets_version" {
type = string
description = "The version of the assets"
}

variable "api_image_tag" {
type = string
description = "The tag of the API image to deploy"
Expand Down
20 changes: 18 additions & 2 deletions infra/terraform/modules/service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,39 @@

## Providers

No providers.
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.0.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_acm"></a> [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 |
| <a name="module_cloudfront"></a> [cloudfront](#module\_cloudfront) | terraform-aws-modules/cloudfront/aws | ~> 3.4 |
| <a name="module_ecs_cluster"></a> [ecs\_cluster](#module\_ecs\_cluster) | terraform-aws-modules/ecs/aws//modules/cluster | ~> 5.10 |
| <a name="module_ecs_service"></a> [ecs\_service](#module\_ecs\_service) | terraform-aws-modules/ecs/aws//modules/service | ~> 5.10 |
| <a name="module_log_bucket"></a> [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 4.0 |
| <a name="module_records"></a> [records](#module\_records) | terraform-aws-modules/route53/aws//modules/records | ~> 2.0 |
| <a name="module_route53_records"></a> [route53\_records](#module\_route53\_records) | terraform-aws-modules/acm/aws | ~> 4.0 |
| <a name="module_s3_one"></a> [s3\_one](#module\_s3\_one) | terraform-aws-modules/s3-bucket/aws | ~> 4.0 |

## Resources

No resources.
| Name | Type |
|------|------|
| [aws_s3_bucket_policy.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
| [aws_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source |
| [aws_cloudfront_log_delivery_canonical_user_id.cloudfront](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_log_delivery_canonical_user_id) | data source |
| [aws_iam_policy_document.s3_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_assets_version"></a> [assets\_version](#input\_assets\_version) | The version of the assets | `string` | n/a | yes |
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | The domain name for the environment | `string` | n/a | yes |
| <a name="input_environment"></a> [environment](#input\_environment) | The environment to deploy to | `string` | n/a | yes |
| <a name="input_services"></a> [services](#input\_services) | The services to deploy | <pre>map(object({<br> image = string<br> cpu = number<br> memory = number<br> security_group_ids = list(string)<br> subnet_ids = list(string)<br> }))</pre> | `{}` | no |

Expand Down
205 changes: 205 additions & 0 deletions infra/terraform/modules/service/cdn.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
provider "aws" {
alias = "acm"

# CloudFront expects ACM resources in us-east-1 region only
region = "us-east-1"

# Make it faster by skipping various bits not important.
skip_metadata_api_check = true
skip_region_validation = true
skip_credentials_validation = true

# skip_requesting_account_id should be disabled to generate valid ARN in apigatewayv2_api_execution_arn
skip_requesting_account_id = false
}

data "aws_route53_zone" "this" {
name = var.domain_name
}

locals {
domain_name = trimsuffix(data.aws_route53_zone.this.name, ".")
subdomain = "cdn"
}

module "acm" {
source = "terraform-aws-modules/acm/aws"
version = "~> 4.0"

domain_name = "${local.subdomain}.${local.domain_name}"
zone_id = data.aws_route53_zone.this.id

create_route53_records = false
validation_record_fqdns = module.route53_records.validation_route53_record_fqdns

providers = {
aws = aws.acm
}
}

module "route53_records" {
source = "terraform-aws-modules/acm/aws"
version = "~> 4.0"

create_certificate = false
create_route53_records_only = true

validation_method = "DNS"

distinct_domain_names = module.acm.distinct_domain_names
zone_id = data.aws_route53_zone.this.id

acm_certificate_domain_validation_options = module.acm.acm_certificate_domain_validation_options
}

module "cloudfront" {
source = "terraform-aws-modules/cloudfront/aws"
version = "~> 3.4"

aliases = ["${local.subdomain}.${local.domain_name}"]

http_version = "http2and3"
is_ipv6_enabled = true

# `PriceClass_100` is most cost efficient for VOL and covers the main region of the VOL user-base (UK).
price_class = "PriceClass_100"

wait_for_deployment = false

# When you enable additional metrics for a distribution, CloudFront sends up to 8 metrics to CloudWatch in the US East (N. Virginia) Region.
# This rate is charged only once per month, per metric (up to 8 metrics per distribution).
create_monitoring_subscription = true

create_origin_access_control = true
origin_access_control = {
s3_oac = {
description = "CloudFront access to S3"
origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
}

logging_config = {
bucket = module.log_bucket.s3_bucket_bucket_domain_name
prefix = "cloudfront"
}

origin = {
s3_oac = {
domain_name = module.s3_one.s3_bucket_bucket_regional_domain_name
origin_access_control = "s3_oac"
origin_path = "/${trimprefix(var.assets_version, "/")}"
}
}

default_cache_behavior = {
target_origin_id = "s3_oac"
viewer_protocol_policy = "allow-all"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]

use_forwarded_values = false

cache_policy_name = "Managed-CachingOptimized"
origin_request_policy_name = "Managed-UserAgentRefererHeaders"
response_headers_policy_name = "Managed-SimpleCORS"
}

ordered_cache_behavior = [
{
path_pattern = "/static/*"
target_origin_id = "s3_oac"
viewer_protocol_policy = "redirect-to-https"

allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]

use_forwarded_values = false

cache_policy_name = "Managed-CachingOptimized"
origin_request_policy_name = "Managed-UserAgentRefererHeaders"
response_headers_policy_name = "Managed-SecurityHeadersPolicy"
},
]

viewer_certificate = {
acm_certificate_arn = module.acm.acm_certificate_arn
ssl_support_method = "sni-only"
}
}

data "aws_canonical_user_id" "current" {}
data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {}

module "s3_one" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 4.0"

bucket = "vol-app-${var.environment}-assets"
force_destroy = true
}

module "log_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 4.0"

bucket = "vol-app-${var.environment}-assets-logs"

control_object_ownership = true
object_ownership = "ObjectWriter"

grant = [{
type = "CanonicalUser"
permission = "FULL_CONTROL"
id = data.aws_canonical_user_id.current.id
}, {
# https://github.com/terraform-providers/terraform-provider-aws/issues/12512
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html
type = "CanonicalUser"
permission = "FULL_CONTROL"
id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id
}]
force_destroy = true
}

module "records" {
source = "terraform-aws-modules/route53/aws//modules/records"
version = "~> 2.0"

zone_id = data.aws_route53_zone.this.zone_id

records = [
{
name = local.subdomain
type = "A"
alias = {
name = module.cloudfront.cloudfront_distribution_domain_name
zone_id = module.cloudfront.cloudfront_distribution_hosted_zone_id
}
},
]
}

data "aws_iam_policy_document" "s3_policy" {
statement {
actions = ["s3:GetObject"]
resources = ["${module.s3_one.s3_bucket_arn}/static/*"]

principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}

condition {
test = "StringEquals"
variable = "aws:SourceArn"
values = [module.cloudfront.cloudfront_distribution_arn]
}
}
}

resource "aws_s3_bucket_policy" "bucket_policy" {
bucket = module.s3_one.s3_bucket_id
policy = data.aws_iam_policy_document.s3_policy.json
}
10 changes: 10 additions & 0 deletions infra/terraform/modules/service/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ variable "environment" {
description = "The environment to deploy to"
}

variable "domain_name" {
type = string
description = "The domain name for the environment"
}

variable "assets_version" {
type = string
description = "The version of the assets"
}

variable "services" {
type = map(object({
image = string
Expand Down

0 comments on commit 74a4dff

Please sign in to comment.