diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index 3cef705e..dab98d8b 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -119,7 +119,7 @@ jobs: TF_VAR_domain_name: ${{ secrets.TF_VAR_domain_name }} TF_VAR_epc_auth_token: ${{ secrets.TF_VAR_epc_auth_token }} TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} - TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key}} + TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key }} run: | ECR_REPO_URL_VAR="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then @@ -155,7 +155,7 @@ jobs: TF_VAR_domain_name: ${{ secrets.TF_VAR_domain_name }} TF_VAR_epc_auth_token: ${{ secrets.TF_VAR_epc_auth_token }} TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} - TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key}} + TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key }} run: | EXTRA_VARS="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index ef927ccb..1d84505b 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -115,7 +115,6 @@ jobs: TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} @@ -343,7 +342,23 @@ jobs: TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} TF_VAR_api_key: ${{ secrets.FASTAPI_API_KEY }} TF_VAR_secret_key: ${{ secrets.NEXTAUTH_SECRET }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} + TF_VAR_domain_name: ${{ secrets.ARA_DEV_DOMAIN_NAME }} TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} + # ============================================================ + # Deploy Cloudfront CDN + # ============================================================ + cloudfront_cdn: + needs: [determine_stage, shared_terraform, fast_api_lambda] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: ara_cdn + lambda_path: infrastructure/terraform/cdn + stage: ${{ needs.determine_stage.outputs.stage }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf new file mode 100644 index 00000000..daa3b0f1 --- /dev/null +++ b/infrastructure/terraform/cdn/main.tf @@ -0,0 +1,55 @@ +############################################ +# Load Shared Terraform State +############################################ +data "terraform_remote_state" "shared" { + backend = "s3" + config = { + bucket = "assessment-model-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +############################################ +# Load FastAPI Terraform State +############################################ +data "terraform_remote_state" "fast_api" { + backend = "s3" + config = { + bucket = "assessment-model-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +############################################ +# CloudFront for API +############################################ +module "cdn" { + source = "../modules/cloudfront" + + # Comment out temporarily just to see what happens + # aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] + + origins = [ + # ---- S3 ---- + { + origin_type = "s3" + origin_domain_name = data.terraform_remote_state.shared.outputs.retrofit_datalake_bucket_domain_name + origin_id = "s3-origin" + bucket_id = data.terraform_remote_state.shared.outputs.retrofit_datalake_bucket_id + bucket_arn = data.terraform_remote_state.shared.outputs.retrofit_datalake_bucket_arn + }, + + # ---- API Gateway ---- + { + origin_type = "api" + origin_domain_name = replace( + data.terraform_remote_state.fast_api.outputs.api_endpoint, + "https://", + "" + ) + origin_id = "api-origin" + } + ] +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn/outputs.tf b/infrastructure/terraform/cdn/outputs.tf new file mode 100644 index 00000000..7c684377 --- /dev/null +++ b/infrastructure/terraform/cdn/outputs.tf @@ -0,0 +1,3 @@ +output "cloudfront_domain_name" { + value = module.api_cdn.cloudfront_domain_name +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn/variables.tf b/infrastructure/terraform/cdn/variables.tf new file mode 100644 index 00000000..423f0b0f --- /dev/null +++ b/infrastructure/terraform/cdn/variables.tf @@ -0,0 +1,3 @@ +variable "stage" { + type = string +} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/engine/main.tf b/infrastructure/terraform/lambda/engine/main.tf index 3f28933b..1f3ce017 100644 --- a/infrastructure/terraform/lambda/engine/main.tf +++ b/infrastructure/terraform/lambda/engine/main.tf @@ -44,7 +44,6 @@ module "lambda" { DB_PORT = var.db_port API_KEY = var.api_key SECRET_KEY = var.secret_key - DOMAIN_NAME = var.domain_name EPC_AUTH_TOKEN = var.epc_auth_token GOOGLE_SOLAR_API_KEY = var.google_solar_api_key diff --git a/infrastructure/terraform/lambda/engine/variables.tf b/infrastructure/terraform/lambda/engine/variables.tf index 0a74ad5b..bf0a42a2 100644 --- a/infrastructure/terraform/lambda/engine/variables.tf +++ b/infrastructure/terraform/lambda/engine/variables.tf @@ -65,10 +65,6 @@ variable "secret_key" { sensitive = true } -variable "domain_name" { - type = string -} - variable "epc_auth_token" { type = string sensitive = true diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 1d5224ea..f71b6f60 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -39,14 +39,6 @@ locals { db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string) } -# data "aws_ssm_parameter" "certificate_arn" { -# name = "/ssl_certificate_arn" -# } - -# data "aws_route53_zone" "this" { -# name = var.domain_name -# } - ############################################ # FastAPI Lambda + API Gateway ############################################ @@ -63,9 +55,7 @@ module "fastapi" { artifact_bucket = data.terraform_remote_state.shared.outputs.ara_fast_api_state_bucket requirements_file = "${path.root}/../../../../backend/app/requirements/requirements.txt" - # domain_name = "api.${var.domain_name}" - # certificate_arn = data.aws_ssm_parameter.certificate_arn.value - # route53_zone_id = data.aws_route53_zone.this.zone_id + domain_name = "api.${var.domain_name}" environment = { ENVIRONMENT = var.stage diff --git a/infrastructure/terraform/lambda/fast-api/outputs.tf b/infrastructure/terraform/lambda/fast-api/outputs.tf new file mode 100644 index 00000000..d3d9dbaa --- /dev/null +++ b/infrastructure/terraform/lambda/fast-api/outputs.tf @@ -0,0 +1,7 @@ +output "domain_name" { + value = module.fastapi.domain_name +} + +output "api_endpoint" { + value = module.fastapi.api_endpoint +} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/variables.tf b/infrastructure/terraform/lambda/fast-api/variables.tf index d329e0ca..a3157590 100644 --- a/infrastructure/terraform/lambda/fast-api/variables.tf +++ b/infrastructure/terraform/lambda/fast-api/variables.tf @@ -29,9 +29,9 @@ variable "secret_key" { sensitive = true } -# variable "domain_name" { -# type = string -# } +variable "domain_name" { + type = string +} variable "epc_auth_token" { type = string diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 281ff09f..02b64606 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -1,65 +1,144 @@ -resource "aws_cloudfront_distribution" "s3_distribution" { - origin { - domain_name = var.bucket_domain_name - origin_id = "S3-${var.bucket_name}" +############################################ +# CloudFront Distribution +############################################ - s3_origin_config { - origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path +resource "aws_cloudfront_distribution" "this" { + + ########################################## + # Origins + ########################################## + + dynamic "origin" { + for_each = { for o in var.origins : o.origin_id => o } + + content { + domain_name = origin.value.origin_domain_name + origin_id = origin.value.origin_id + + ###################################### + # S3 Origin + ###################################### + dynamic "s3_origin_config" { + for_each = origin.value.origin_type == "s3" ? [1] : [] + + content { + origin_access_identity = + aws_cloudfront_origin_access_identity.oai[origin.key] + .cloudfront_access_identity_path + } + } + + ###################################### + # API Gateway Origin + ###################################### + dynamic "custom_origin_config" { + for_each = origin.value.origin_type == "api" ? [1] : [] + + content { + http_port = 80 + https_port = 443 + origin_protocol_policy = "https-only" + origin_ssl_protocols = ["TLSv1.2"] + } + } } } enabled = true + aliases = var.aliases + + ########################################## + # Default Cache Behavior + ########################################## default_cache_behavior { - allowed_methods = ["GET", "HEAD"] - cached_methods = ["GET", "HEAD"] - target_origin_id = "S3-${var.bucket_name}" + target_origin_id = var.origins[0].origin_id + viewer_protocol_policy = "redirect-to-https" - compress = true + + allowed_methods = [ + "GET", + "HEAD" + ] + + cached_methods = [ + "GET", + "HEAD" + ] forwarded_values { - query_string = false + query_string = true + headers = ["*"] + cookies { - forward = "none" + forward = "all" } } - min_ttl = 0 - default_ttl = 86400 - max_ttl = 31536000 + compress = true + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 } price_class = "PriceClass_All" + ########################################## + # Geo Restrictions + ########################################## + restrictions { geo_restriction { restriction_type = "none" } } + ########################################## + # SSL Certificate + ########################################## + viewer_certificate { cloudfront_default_certificate = true } } +############################################ +# Origin Access Identities (S3 only) +############################################ + resource "aws_cloudfront_origin_access_identity" "oai" { - comment = "OAI for ${var.bucket_name}" + for_each = { + for o in var.origins : o.origin_id => o + if o.origin_type == "s3" + } + + comment = "OAI for ${each.key}" } +############################################ +# S3 Bucket Policy (S3 only) +############################################ + resource "aws_s3_bucket_policy" "bucket_policy" { - bucket = var.bucket_id + for_each = { + for o in var.origins : o.origin_id => o + if o.origin_type == "s3" + } + + bucket = each.value.bucket_id policy = jsonencode({ - Version = "2012-10-17" + Version = "2012-10-17" Statement = [ { - Effect = "Allow" + Effect = "Allow" Principal = { - AWS = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${aws_cloudfront_origin_access_identity.oai.id}" + AWS = aws_cloudfront_origin_access_identity.oai[each.key] + .iam_arn } Action = "s3:GetObject" - Resource = "${var.bucket_arn}/*" - }, + Resource = "${each.value.bucket_arn}/*" + } ] }) -} +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront/variables.tf b/infrastructure/terraform/modules/cloudfront/variables.tf index 88f770a8..feff2faa 100644 --- a/infrastructure/terraform/modules/cloudfront/variables.tf +++ b/infrastructure/terraform/modules/cloudfront/variables.tf @@ -1,24 +1,14 @@ -variable "bucket_name" { - description = "The name of the bucket" - type = string +variable "origins" { + type = list(object({ + origin_type = string # "s3" or "api" + origin_domain_name = string + origin_id = string + + bucket_id = optional(string) + bucket_arn = optional(string) + })) } -variable "stage" { - description = "The deployment stage" - type = string -} - -variable "bucket_id" { - description = "The ID of the S3 bucket" - type = string -} - -variable "bucket_arn" { - description = "The ARN of the S3 bucket" - type = string -} - -variable "bucket_domain_name" { - description = "The regional domain name of the S3 bucket" - type = string +variable "aliases" { + type = list(string) } \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index f33f8d5b..b1ee3b75 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -90,37 +90,3 @@ resource "aws_lambda_permission" "apigw_invoke" { principal = "apigateway.amazonaws.com" source_arn = "${aws_apigatewayv2_api.this.execution_arn}/*/*" } - -############################################ -# Custom domain -############################################ -# resource "aws_apigatewayv2_domain_name" "this" { -# count = var.domain_name != null ? 1 : 0 -# domain_name = var.domain_name - -# domain_name_configuration { -# certificate_arn = var.certificate_arn -# endpoint_type = "REGIONAL" -# security_policy = "TLS_1_2" -# } -# } - -# resource "aws_apigatewayv2_api_mapping" "this" { -# count = var.domain_name != null ? 1 : 0 -# api_id = aws_apigatewayv2_api.this.id -# domain_name = aws_apigatewayv2_domain_name.this[0].id -# stage = aws_apigatewayv2_stage.this.id -# } - -# resource "aws_route53_record" "this" { -# count = var.domain_name != null ? 1 : 0 -# name = aws_apigatewayv2_domain_name.this[0].domain_name -# type = "A" -# zone_id = var.route53_zone_id - -# alias { -# name = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].target_domain_name -# zone_id = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].hosted_zone_id -# evaluate_target_health = false -# } -# } \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index 9ced7c8b..2d7af141 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -6,6 +6,14 @@ output "api_endpoint" { value = aws_apigatewayv2_stage.this.invoke_url } +output "cloudfront_domain" { + value = aws_cloudfront_distribution.api.domain_name +} + +output "certificate_validation_records" { + value = aws_acm_certificate.this.domain_validation_options +} + # output "custom_domain_endpoint" { # value = var.domain_name != null ? "https://${var.domain_name}" : null # } \ No newline at end of file diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 0a6dfe3f..1de2031f 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -127,6 +127,22 @@ module "s3" { allowed_origins = var.allowed_origins } +output "retrofit_datalake_bucket_id" { + value = module.s3.bucket_id +} + +output "retrofit_datalake_bucket_arn" { + value = module.s3.bucket_arn +} + +output "retrofit_datalake_bucket_domain_name" { + value = module.s3.bucket_domain_name +} + +output "retrofit_datalake_bucket_name" { + value = module.s3.bucket_name +} + module "model_directory" { source = "../modules/s3" bucketname = "retrofit-model-directory-${var.stage}" @@ -311,18 +327,6 @@ module "sap_baseline_ecr" { source = "../modules/ecr" } -############################################## -# CDN - Cloudfront -############################################## -module "cloudfront_distribution" { - source = "../modules/cloudfront" - bucket_name = module.s3.bucket_name - bucket_id = module.s3.bucket_id - bucket_arn = module.s3.bucket_arn - bucket_domain_name = module.s3.bucket_domain_name - stage = var.stage -} - ################################################ # SES - Email sending ################################################