diff --git a/.github/workflows/deploy_to_aws.yml b/.github/workflows/deploy_to_aws.yml index 2def34ede..82a9aa242 100644 --- a/.github/workflows/deploy_to_aws.yml +++ b/.github/workflows/deploy_to_aws.yml @@ -156,6 +156,29 @@ jobs: working-directory: src/react run: yarn run build + - id: project + uses: Entepotenz/change-string-case-action-min-dependencies@v1 + with: + string: ${{ vars.PROJECT }} + + - name: Move static + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + FRONTEND_BUCKET: ${{ steps.project.outputs.lowercase }}-${{ steps.get_env_name.outputs.lowercase }}-frontend + AWS_DEFAULT_REGION: "eu-west-1" + CLOUDFRONT_DOMAIN: ${{ vars.CLOUDFRONT_DOMAIN }} + run: | + for id in $(aws cloudfront list-distributions --query "DistributionList.Items[*].Id" --output text); do + domains=$(aws cloudfront get-distribution-config --id $id --query "DistributionConfig.Aliases.Items" --output text) + if [[ "$domains" == "$CLOUDFRONT_DOMAIN" ]]; then + echo "Found Distribution ID: $id for Domain: $CLOUDFRONT_DOMAIN" + CLOUDFRONT_DISTRIBUTION_ID=$id + fi + done + aws s3 sync src/react/build/ s3://$FRONTEND_BUCKET-$AWS_DEFAULT_REGION/ --delete --acl public-read + aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/*" + - name: Move static run: mv src/react/build src/django/static diff --git a/deployment/terraform/cdn.tf b/deployment/terraform/cdn.tf index a047e4736..a71903a1a 100644 --- a/deployment/terraform/cdn.tf +++ b/deployment/terraform/cdn.tf @@ -1,7 +1,52 @@ +locals { + frontend_bucket_name = "${lower(replace(var.project, " ", ""))}-${lower(var.environment)}-frontend-${var.aws_region}" +} + +resource "aws_s3_bucket" "react" { + bucket = local.frontend_bucket_name +} + +resource "aws_s3_bucket_acl" "react_acl" { + bucket = aws_s3_bucket.react.id + acl = "private" +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "react" { + bucket = aws_s3_bucket.react.id + + rule { + bucket_key_enabled = false + + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_s3_bucket_versioning" "react" { + bucket = aws_s3_bucket.react.id + + versioning_configuration { + status = "Suspended" + } +} + +resource "aws_s3_bucket_ownership_controls" "react" { + bucket = aws_s3_bucket.react.id + + rule { + object_ownership = "BucketOwnerPreferred" + } +} + + resource "aws_cloudfront_distribution" "cdn" { depends_on = [ aws_s3_bucket.logs ] + + default_root_object = "index.html" + origin { domain_name = "origin.${local.domain_name}" origin_id = "originAlb" @@ -19,6 +64,23 @@ resource "aws_cloudfront_distribution" "cdn" { } } + origin { + domain_name = aws_s3_bucket.react.bucket_regional_domain_name + origin_id = "originS3" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "https-only" + origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"] + } + + custom_header { + name = "X-CloudFront-Auth" + value = var.cloudfront_auth_token + } + } + enabled = true is_ipv6_enabled = true http_version = "http2" @@ -28,9 +90,9 @@ resource "aws_cloudfront_distribution" "cdn" { aliases = [local.domain_name] default_cache_behavior { - allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] - cached_methods = ["GET", "HEAD", "OPTIONS"] - target_origin_id = "originAlb" + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "originS3" forwarded_values { query_string = true @@ -71,19 +133,18 @@ resource "aws_cloudfront_distribution" "cdn" { ordered_cache_behavior { path_pattern = "tile/*" - allowed_methods = ["GET", "HEAD", "OPTIONS"] - cached_methods = ["GET", "HEAD", "OPTIONS"] + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD", "OPTIONS"] target_origin_id = "originAlb" forwarded_values { query_string = true - headers = ["Referer"] # To discourage hotlinking to cached tiles + headers = ["Referer"] # To discourage hotlinking to cached tiles cookies { forward = "none" } } - compress = true viewer_protocol_policy = "redirect-to-https" min_ttl = 0 @@ -91,6 +152,292 @@ resource "aws_cloudfront_distribution" "cdn" { max_ttl = 31536000 # 1 year. Same as TILE_CACHE_MAX_AGE_IN_SECONDS in src/django/oar/settings.py } + ordered_cache_behavior { + path_pattern = "api/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/api-auth/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/api-token-auth/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/api-feature-flags/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/web/environment.js" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/admin/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/health-check/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/rest-auth/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/user-login/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/user-logout/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/user-signup/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/user-profile/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + + ordered_cache_behavior { + path_pattern = "/user-api-info/*" + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD", "OPTIONS"] + target_origin_id = "originAlb" + + forwarded_values { + query_string = true + headers = ["*"] # To discourage hotlinking to cached tiles + + cookies { + forward = "all" + } + } + + compress = true + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 300 + } + logging_config { include_cookies = false bucket = aws_s3_bucket.logs.bucket_domain_name @@ -114,4 +461,3 @@ resource "aws_cloudfront_distribution" "cdn" { Environment = var.environment } } -