diff --git a/.github/workflows/build-full-environment.yml b/.github/workflows/build-full-environment.yml index 240c611bb..46c1ba539 100644 --- a/.github/workflows/build-full-environment.yml +++ b/.github/workflows/build-full-environment.yml @@ -25,14 +25,7 @@ on: description: Additional tag to add to built images jobs: - wfprev-ui: - uses: ./.github/workflows/mvn-build.yml - secrets: inherit - with: - COMPONENT_NAME: wfprev-war - TAG: latest - COMPONENT_TYPE: client - + wfprev-api: uses: ./.github/workflows/mvn-build.yml secrets: inherit @@ -49,8 +42,17 @@ jobs: terragrunt-deploy-dev: uses: ./.github/workflows/terragrunt-deploy.yml - needs: [wfprev-ui, wfprev-api, liquibase-build] + needs: [wfprev-api, liquibase-build] + with: + DEFAULT_APPLICATION_ENVIRONMENT: dev + IMAGE_TAG: latest + secrets: inherit + + wfprev-ui: + uses: ./.github/workflows/client-build.yml + needs: [terragrunt-deploy-dev] with: DEFAULT_APPLICATION_ENVIRONMENT: dev IMAGE_TAG: latest secrets: inherit + diff --git a/.github/workflows/client-build.yml b/.github/workflows/client-build.yml new file mode 100644 index 000000000..93f5197c0 --- /dev/null +++ b/.github/workflows/client-build.yml @@ -0,0 +1,85 @@ +name: Deploy Angular App to S3 and CloudFront + +on: + push: + branches: + - main # Adjust to your deployment branch + workflow_call: + inputs: + DEFAULT_APPLICATION_ENVIRONMENT: + required: true + type: string + IMAGE_TAG: + required: true + type: string + +env: + TF_VERSION: 1.8.5 + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE_TO_ASSUME }} + role-session-name: wfprev-terraform-s3 + aws-region: ca-central-1 + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ env.TF_VERSION }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Build Angular App + run: npm run build --prod + + - name: Initialize Terraform + run: terraform init + + # Fetch CloudFront Distribution ID in order to invalidate cache + - name: Fetch CloudFront Distribution ID + id: get_cf_id + run: | + export CLOUDFRONT_ID=$(terraform output -raw cloudfront_distribution_id) + echo "CLOUDFRONT_DISTRIBUTION_ID=$CLOUDFRONT_ID" >> $GITHUB_ENV + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_S3_PUSH_ROLE }} + role-session-name: wfprev-push-s3 + aws-region: ca-central-1 + + # this will require the bucket to exist + # so terraform step will need to run first + - name: Sync files to S3 + run: | + aws s3 sync ./dist/wfprev s3://wfprev_site_bucket \ + --delete \ + --cache-control max-age=31536000,public \ + --exclude index.html + aws s3 cp ./dist/wfprev/index.html s3://wfprev_site_bucket/index.html \ + --cache-control max-age=0,no-cache,no-store,must-revalidate + + - name: Invalidate CloudFront Cache + run: | + aws cloudfront create-invalidation \ + --distribution-id ${{ env.CLOUDFRONT_DISTRIBUTION_ID }} \ + --paths "/*" + # see distribution ID section in terraform scripts + # Like the sync, this means we need to run terraform first, then + # trigger this action with the returned distribution ID diff --git a/.github/workflows/liquibase-build.yml b/.github/workflows/liquibase-build.yml index 5f3b4c8f5..1574df324 100644 --- a/.github/workflows/liquibase-build.yml +++ b/.github/workflows/liquibase-build.yml @@ -38,7 +38,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: trimBranchName name: trim branch name if necessary diff --git a/.github/workflows/mvn-build.yml b/.github/workflows/mvn-build.yml index 58d328ebf..10b5c674d 100644 --- a/.github/workflows/mvn-build.yml +++ b/.github/workflows/mvn-build.yml @@ -24,7 +24,6 @@ on: type: choice options: - server - - client - libs workflow_call: inputs: @@ -44,16 +43,16 @@ jobs: environment: name: ${{ inputs.ENVIRONMENT }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ inputs.IS_HOTFIX != 'true' }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ inputs.IS_HOTFIX == 'true' }} with: ref: ${{ inputs.BRANCH }} - name: Set up Node.js 20.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '20.x' @@ -65,29 +64,6 @@ jobs: - name: Add .npmrc file run: echo -e $NPMRC > ~/.npmrc - - - - name: Install dependencies for Angular project - if: ${{ inputs.COMPONENT_TYPE == 'client' }} - run: | - cd ${{ inputs.COMPONENT_TYPE }}/wfprev-war/src/main/angular - npm install - - name: Build Angular app - if: ${{ inputs.COMPONENT_TYPE == 'client' }} - run: | - cd ${{ inputs.COMPONENT_TYPE }}/wfprev-war/src/main/angular - npm run build -- --configuration=production --base-href=/pub/wfprev/ - - - name: Copy client built files to necessary folder - if: ${{ inputs.COMPONENT_TYPE == 'client' }} - run: mkdir -p staging-client && cp -r ${{ inputs.COMPONENT_TYPE }}/wfprev-war/src/main/angular/dist/wfprev/* staging-client/ - - - name: Upload client artifact - if: ${{ inputs.COMPONENT_TYPE == 'client' }} - uses: actions/upload-artifact@v3 - with: - name: ${{ inputs.COMPONENT_NAME }}-package - path: staging-client - name: Build API with Maven if: ${{ inputs.COMPONENT_TYPE == 'server' }} @@ -121,7 +97,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ github.event.inputs.IS_HOTFIX != 'true' }} - id: trimBranchName @@ -131,20 +107,11 @@ jobs: trimTag="${branchTag##*/}" echo "BRANCH_TAG=$trimTag" >> $GITHUB_OUTPUT - # - uses: actions/checkout@v3 + # - uses: actions/checkout@v4 # if: ${{ github.event.inputs.IS_HOTFIX == 'true' }} # with: # ref: ${{ inputs.BRANCH }} - - # Download client artifact - - name: download client artifact - uses: actions/download-artifact@v3 - if: ${{ inputs.COMPONENT_TYPE == 'client' }} - with: - name: wfprev-war-package - path: client/wfprev-war/dist/wfprev - - name: download server artifact uses: actions/download-artifact@v3 with: @@ -180,17 +147,6 @@ jobs: type=ref,event=tag type=raw,value=${{ inputs.TAG }} - - name: Build and push Client Docker image - if: ${{ inputs.COMPONENT_TYPE == 'client' }} - uses: docker/build-push-action@v6 - with: - context: client/wfprev-war - build-args: | - CONTAINER_NAME=wfprev-war - push: true - tags: ${{ steps.meta_pr.outputs.tags }} - labels: ${{ steps.meta_pr.outputs.labels }} - - name: Build and push Docker image uses: docker/build-push-action@v6 with: diff --git a/.github/workflows/terragrunt-deploy.yml b/.github/workflows/terragrunt-deploy.yml index 12edd5628..e705ff5cf 100644 --- a/.github/workflows/terragrunt-deploy.yml +++ b/.github/workflows/terragrunt-deploy.yml @@ -61,7 +61,7 @@ jobs: contents: read # This is required for actions/checkout steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # - name: retrieve lambda artifacts # uses: actions/download-artifact@v3 # with: @@ -105,7 +105,7 @@ jobs: echo "IMAGE_UI_BY_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE)" >> $GITHUB_OUTPUT - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE_TO_ASSUME }} role-session-name: wfprev-terraform-s3 @@ -114,7 +114,6 @@ jobs: - uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ env.TF_VERSION }} - cli_config_credentials_token: ${{ secrets.TFC_TEAM_TOKEN }} - uses: peter-murray/terragrunt-github-action@v1.0.0 with: @@ -149,8 +148,6 @@ jobs: # WFPREV UI CLIENT_IMAGE: ${{ steps.getDigestUI.outputs.IMAGE_UI_BY_DIGEST }} WEBADE_OAUTH2_WFPREV_UI_CLIENT_SECRET: ${{ secrets.WEBADE_OAUTH2_WFPREV_UI_CLIENT_SECRET }} - WFPREV_CLIENT_MEMORY: ${{vars.WFPREV_CLIENT_MEMORY}} - WFPREV_CLIENT_CPU_UNITS : ${{vars.WFPREV_CLIENT_CPU_UNITS}} # DB WFPREV_USERNAME: ${{secrets.WFPREV_USERNAME}} diff --git a/terraform/README.md b/terraform/README.md index afc4ea4bd..578648a0e 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -7,6 +7,9 @@ The api_gateway.tf file configures an API Gateway to expose the WFPREV API for e autoscaling.tf: The autoscaling.tf file sets up auto scaling for multiple ECS services within the WFPrev application, including the main service, nginx, and client. It utilizes AWS Application Auto Scaling to dynamically adjust the number of tasks running in each service based on CPU utilization. The file defines scaling targets, step scaling policies for both scaling up and down, and associated CloudWatch alarms. These alarms monitor CPU utilization, triggering scale-up actions when utilization exceeds 50% and scale-down actions when it falls below 10%. The configuration allows each service to scale between 1 and 10 tasks, with a 60-second cooldown period between scaling actions. This setup ensures that the application can efficiently handle varying loads while optimizing resource usage and costs. +cloudfront.tf: +The cloudfront.tf file defines a CloudFront distribution to serve content from an S3 bucket securely, using an Origin Access Identity (OAI) to restrict bucket access. It specifies caching behavior with customizable TTL settings, enforces HTTPS via a viewer protocol policy, and restricts query string and cookie forwarding to optimize performance. The configuration enables IPv6, sets index.html as the default root object, and uses a default CloudFront certificate for HTTPS. Additionally, it outputs the distribution ID, making it easy to reference in other workflows or scripts. + ecs.tf: This file sets up an ECS cluster for deploying the WFPREV server and client applications using FARGATE and FARGATE_SPOT capacity providers. It defines task definitions for both server and client containers, including resource allocations, environment variables, and logging configurations. The wfprev_client and wfprev_server task definitons here are setting iam roles for execution, network mode, provisionsing CPU and memory allocation, and setting volumes on the cluster for deployment of the client and server. The wfprev-liquibase task definition does the same allocation and runs database migrations. The file also creates ECS services to manage these tasks, linking them to the appropriate load balancers for traffic routing and health checks. The network condiguration sets a custom wfprev_tomcat_access security group, subnets and assigns the container a public IP address. This file also includes logConfiguration sections within the container definitions for both the WFPrev server and client task definitions. These configurations use the "awslogs" log driver, which is designed to send container logs directly to Amazon CloudWatch Logs. The options specified in each logConfiguration block determine how the logs are organized in CloudWatch. The "awslogs-create-group" option set to "true" allows ECS to automatically create the log group if it doesn't exist. The "awslogs-group" option specifies the name of the log group in CloudWatch where the logs will be sent, typically following the pattern "/ecs/{container-name}". The "awslogs-region" option ensures logs are sent to the correct AWS region, while "awslogs-stream-prefix" adds a prefix to the log stream names for easier identification. With these configurations in place, when the ECS tasks run, the Amazon ECS container agent automatically collects the stdout and stderr outputs from the containers and sends them to CloudWatch Logs without any additional setup required. This seamless integration allows for centralized logging and monitoring of ECS tasks. The CloudWatch logs can be accessed in the Log groups section of the CloudWatch service in the AWS console. @@ -16,8 +19,17 @@ This file defines IAM roles and policies to grant necessary permissions for ECS main.tf: The main.tf file sets up the Terraform configuration, specifying the required AWS provider version (~> 4.0) and ensuring Terraform itself is version 1.1.0 or higher. This establishes the foundation for managing AWS resources within the project +rds.tf: +This file sets up a PostgreSQL database instance in AWS, along with its subnet group. The database is configured for high availability with multi-AZ support, encrypted storage, and automated backups with a retention period of 7 days. It is not publicly accessible and uses a specified VPC security group for access. CloudWatch logging is enabled for monitoring, and the configuration allows updates to major versions but skips the final snapshot upon deletion. The associated subnet group defines the subnets in which the database operates. + +s3.tf: +The s3.tf file creates a public S3 bucket for website hosting, with index.html as the default and error page. It includes a policy for public read access and outputs the bucket name for use in other workflows. Asset uploads are intended to be handled via GitHub Actions. + secrets.tf: The secrets.tf file manages GitHub credentials in AWS Secrets Manager. It creates a secret named bcws_wfprev_creds_${var.TARGET_ENV} and stores the GitHub username and token in a secret version for secure access and management. +security.tf: +The security.tf file defines security groups for a web application environment. It includes data lookups for existing security groups (Web_sg, App_sg, and Data_sg) and creates two new security groups. The wfprev_tomcat_access group allows unrestricted access on a specified API port, while the jumphost group permits RDP access (port 3389) from the Web_sg and App_sg security groups within the same VPC. These groups help manage and secure traffic flow in the environment. + terragrunt-deploy.yml: -The terragrunt-deploy.yml GitHub Actions workflow automates the deployment process for the WFPREV application using Terragrunt. It supports multiple environments (dev, test, prod) and various configuration options, such as image tags and schema names. The workflow sets up AWS credentials, Terraform, and Terragrunt, then applies the Terragrunt configurations to deploy the application infrastructure and services. It also determines which Liquibase command to run for database changes and manages environment-specific variables for the deployment process. \ No newline at end of file +The terragrunt-deploy.yml GitHub Actions workflow automates the deployment process for the WFPREV application using Terragrunt. It supports multiple environments (dev, test, prod) and various configuration options, such as image tags and schema names. The workflow sets up AWS credentials, Terraform, and Terragrunt, then applies the Terragrunt configurations to deploy the application infrastructure and services. It also determines which Liquibase command to run for database changes and manages environment-specific variables for the deployment process. After the Liquibase and API deployments are completed via Terraform and Terragrunt, the UI portion of the application is built and the distribution files are stored as static resources via an S3 bucket, which is served via CloudFront, a content delivery network (CDN) that caches content closer to users through a globally distributed network of edge locations. \ No newline at end of file diff --git a/terraform/alb.tf b/terraform/alb.tf index 1d4e5d940..fc192be40 100644 --- a/terraform/alb.tf +++ b/terraform/alb.tf @@ -70,21 +70,6 @@ resource "aws_lb_listener_rule" "wfprev-api" { } } -resource "aws_lb_listener_rule" "wfprev-ui" { - listener_arn = aws_lb_listener.wfprev_main.arn - - action { - type = "forward" - target_group_arn = aws_alb_target_group.wfprev_ui.arn - } - - condition { - path_pattern { - values = [for sn in var.PREVENTION_WAR_NAMES : "/${aws_apigatewayv2_stage.wfprev_stage.name}/${sn}"] - } - } -} - ////////////////////////////// /// TARGET GROUP RESOURCES /// ////////////////////////////// @@ -108,22 +93,3 @@ resource "aws_alb_target_group" "wfprev_api" { # tags = local. } - -resource "aws_alb_target_group" "wfprev_ui" { - name = "wfprev-ui-${var.TARGET_ENV}" - port = var.WFPREV_CLIENT_PORT - protocol = "HTTP" - vpc_id = module.network.aws_vpc.id - target_type = "ip" - deregistration_delay = 30 - - health_check { - healthy_threshold = "2" - interval = "300" - protocol = "HTTP" - matcher = "200" - timeout = "3" - path = "/${aws_apigatewayv2_stage.wfprev_stage.name}/${var.PREVENTION_WAR_NAMES[0]}/" - unhealthy_threshold = "2" - } -} \ No newline at end of file diff --git a/terraform/autoscaling.tf b/terraform/autoscaling.tf index 93d61173d..42dac5cbe 100644 --- a/terraform/autoscaling.tf +++ b/terraform/autoscaling.tf @@ -94,99 +94,3 @@ resource "aws_cloudwatch_metric_alarm" "wfprev_service_cpu_low" { Environment = "${var.TARGET_ENV}" } } - -resource "aws_appautoscaling_target" "wfprev_client_target" { - service_namespace = "ecs" - resource_id = "service/${aws_ecs_cluster.wfprev_main.name}/${aws_ecs_service.client.name}" - scalable_dimension = "ecs:service:DesiredCount" - min_capacity = 1 - max_capacity = 10 -} - -# Automatically scale capacity up by one -resource "aws_appautoscaling_policy" "wfprev_client_up" { - name = "wfprev_client_scale_up" - service_namespace = "ecs" - resource_id = "service/${aws_ecs_cluster.wfprev_main.name}/${aws_ecs_service.client.name}" - scalable_dimension = "ecs:service:DesiredCount" - - step_scaling_policy_configuration { - adjustment_type = "ChangeInCapacity" - cooldown = 60 - metric_aggregation_type = "Maximum" - - step_adjustment { - metric_interval_lower_bound = 0 - scaling_adjustment = 1 - } - } - - depends_on = [aws_appautoscaling_target.wfprev_client_target] - } - -# Automatically scale capacity down by one -resource "aws_appautoscaling_policy" "wfprev_client_down" { - name = "wfprev_client_scale_down" - service_namespace = "ecs" - resource_id = "service/${aws_ecs_cluster.wfprev_main.name}/${aws_ecs_service.client.name}" - scalable_dimension = "ecs:service:DesiredCount" - - step_scaling_policy_configuration { - adjustment_type = "ChangeInCapacity" - cooldown = 60 - metric_aggregation_type = "Maximum" - - step_adjustment { - metric_interval_upper_bound = 0 - scaling_adjustment = -1 - } - } - - depends_on = [aws_appautoscaling_target.wfprev_client_target] - } - -# CloudWatch alarm that triggers the autoscaling down policy -resource "aws_cloudwatch_metric_alarm" "wfprev_client_service_cpu_low" { - alarm_name = "wfprev_client_cpu_utilization_low" - comparison_operator = "LessThanOrEqualToThreshold" - evaluation_periods = "2" - metric_name = "CPUUtilization" - namespace = "AWS/ECS" - period = "60" - statistic = "Average" - threshold = "10" - - dimensions = { - ClusterName = aws_ecs_cluster.wfprev_main.name - ServiceName = aws_ecs_service.client.name - } - - alarm_actions = [aws_appautoscaling_policy.wfprev_client_down.arn] - - tags = { - Environment = "${var.TARGET_ENV}" - } - } - -# CloudWatch alarm that triggers the autoscaling up policy -resource "aws_cloudwatch_metric_alarm" "wfprev_client_service_cpu_high" { - alarm_name = "wfprev_client_cpu_utilization_high" - comparison_operator = "GreaterThanOrEqualToThreshold" - evaluation_periods = "1" - metric_name = "CPUUtilization" - namespace = "AWS/ECS" - period = "60" - statistic = "Average" - threshold = "50" - - dimensions = { - ClusterName = aws_ecs_cluster.wfprev_main.name - ServiceName = aws_ecs_service.client.name - } - - alarm_actions = [aws_appautoscaling_policy.wfprev_client_up.arn] - - tags = { - Environment = "${var.TARGET_ENV}" - } - } \ No newline at end of file diff --git a/terraform/cloudfront.tf b/terraform/cloudfront.tf new file mode 100644 index 000000000..d7847deef --- /dev/null +++ b/terraform/cloudfront.tf @@ -0,0 +1,56 @@ +# CloudFront Origin Access Identity (OAI) for secure access to S3 +resource "aws_cloudfront_origin_access_identity" "oai" { + comment = "OAI for wfprev UI" +} + +# CloudFront Distribution +resource "aws_cloudfront_distribution" "wfprev_app_distribution" { + origin { + domain_name = aws_s3_bucket.wfprev_site_bucket.bucket_regional_domain_name + origin_id = "S3-${aws_s3_bucket.wfprev_site_bucket.id}" + + s3_origin_config { + origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path + } + } + + enabled = true + is_ipv6_enabled = true + default_root_object = "index.html" + + # Configure cache behaviors + default_cache_behavior { + allowed_methods = ["GET", "HEAD", "OPTIONS"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "S3-${aws_s3_bucket.wfprev_site_bucket.id}" + viewer_protocol_policy = "redirect-to-https" + + forwarded_values { + query_string = false + + cookies { + forward = "none" + } + } + + min_ttl = 0 + default_ttl = 86400 + max_ttl = 31536000 + } + + # Viewer Certificate + viewer_certificate { + cloudfront_default_certificate = true + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } +} + +output "cloudfront_distribution_id" { + value = aws_cloudfront_distribution.wfprev_app_distribution.id +} + diff --git a/terraform/ecs.tf b/terraform/ecs.tf index e9dace46e..a1a8f5913 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -67,14 +67,6 @@ resource "aws_ecs_task_definition" "wfprev_server" { name = "AWS_REGION" value = var.AWS_REGION }, - { - name = "WFPREV_CLIENT_ID" - value = var.WFPREV_CLIENT_ID - }, - { - name = "WFPREV_CLIENT_SECRET", - value = var.WFPREV_CLIENT_SECRET - }, { name = "WEBADE_OAUTH2_CHECK_TOKEN_URL" value = var.WEBADE_OAUTH2_CHECK_TOKEN_URL @@ -143,100 +135,6 @@ resource "aws_ecs_task_definition" "wfprev_server" { }]) } -# WFPrev Client Task Definition - -resource "aws_ecs_task_definition" "wfprev_client" { - family = "wfprev-client-task-${var.TARGET_ENV}" - execution_role_arn = aws_iam_role.wfprev_ecs_task_execution_role.arn - task_role_arn = aws_iam_role.wfprev_app_container_role.arn - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = var.WFPREV_CLIENT_CPU_UNITS - memory = var.WFPREV_CLIENT_MEMORY - volume { - name = "work" - } - volume { - name = "logging" - } - - container_definitions = jsonencode([ - { - essential = true - readonlyRootFilesystem = false - name = var.client_container_name - image = var.CLIENT_IMAGE - cpu = var.WFPREV_CLIENT_CPU_UNITS - memory = var.WFPREV_CLIENT_MEMORY - networkMode = "awsvpc" - portMappings = [ - { - protocol = "tcp" - containerPort = var.WFPREV_CLIENT_PORT - hostPort = var.WFPREV_CLIENT_PORT - } - ] - environment = [ - { - name = "LOGGING_LEVEL" - value = "${var.LOGGING_LEVEL}" - }, - { - name = "AWS_REGION", - value = var.AWS_REGION - }, - { - #Base URL will use the - name = "BASE_URL", - value = var.TARGET_ENV == "prod" ? "https://${var.gov_client_url}/" : "${aws_apigatewayv2_stage.wfprev_stage.invoke_url}/wfprev-ui" - }, - { - name = "WEBADE_OAUTH2_WFPREV_REST_CLIENT_SECRET", - value = var.WEBADE_OAUTH2_WFPREV_UI_CLIENT_SECRET - }, - { - name = "WEBADE-OAUTH2_TOKEN_URL", - value = var.WEBADE-OAUTH2_TOKEN_URL - }, - { - name = "WEBADE-OAUTH2_CHECK_TOKEN_V2_URL" - value = var.WEBADE_OAUTH2_CHECK_TOKEN_URL - }, - { //Will be phased out from prod eventually, but not yet "https://${aws_route53_record.wfprev_nginx.name}/" - name = "WFPREV_API_URL", - value = var.TARGET_ENV == "prod" ? "https://${var.gov_api_url}/" : "https://example.com/" - }, - { - name = "APPLICATION_ENVIRONMENT", - value = var.TARGET_ENV != "prod" ? var.TARGET_ENV : " " - }, - ] - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-create-group = "true" - awslogs-group = "/ecs/${var.client_name}" - awslogs-region = var.AWS_REGION - awslogs-stream-prefix = "ecs" - } - } - mountPoints = [ - { - sourceVolume = "logging" - containerPath = "/usr/local/tomcat/logs" - readOnly = false - }, - { - sourceVolume = "work" - containerPath = "/usr/local/tomcat/work" - readOnly = false - } - ] - volumesFrom = [] - } - ]) -} - # WFPrev Liquibase Task Definition resource "null_resource" "always_run" { @@ -364,44 +262,5 @@ resource "aws_ecs_service" "wfprev_server" { # tags = local.common_tags } -# ECS Service for WFPrev Client - -resource "aws_ecs_service" "client" { - name = "wfprev-client-service-${var.TARGET_ENV}" - cluster = aws_ecs_cluster.wfprev_main.id - task_definition = aws_ecs_task_definition.wfprev_client.arn - desired_count = var.APP_COUNT - enable_ecs_managed_tags = true - propagate_tags = "TASK_DEFINITION" - health_check_grace_period_seconds = 60 - wait_for_steady_state = false - - - capacity_provider_strategy { - capacity_provider = "FARGATE_SPOT" - weight = 80 - } - capacity_provider_strategy { - capacity_provider = "FARGATE" - weight = 20 - base = 1 - } - - - network_configuration { - security_groups = [data.aws_security_group.app.id, aws_security_group.wfprev_tomcat_access.id] - subnets = module.network.aws_subnet_ids.app.ids - assign_public_ip = true - } - - load_balancer { - target_group_arn = aws_alb_target_group.wfprev_ui.id - container_name = var.client_container_name - container_port = var.WFPREV_CLIENT_PORT - } - - # depends_on = [aws_iam_role_policy_attachment.wfprev_ecs_task_execution_role] -} - # Placeholder for other ECS Services like Nginx, Liquibase, etc. # Define similar ECS services for additional task definitions. diff --git a/terraform/iam.tf b/terraform/iam.tf index 7fd3ccf9c..7dfba1bd0 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -39,7 +39,7 @@ resource "aws_iam_role" "wfprev_ecs_task_execution_role" { } resource "aws_iam_role_policy_attachment" "wfprev_ecs_task_execution_changelogs" { - role = aws_iam_role.wfprev_ecs_task_execution_role.name + role = aws_iam_role.wfprev_ecs_task_execution_role.name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } @@ -63,4 +63,36 @@ resource "aws_iam_role_policy" "wfprev_ecs_task_execution_cwlogs" { ] } EOF -} \ No newline at end of file +} + +# IAM User for GitHub Actions (with limited permissions - Cloudfront invalidate, bucket cleanup) +resource "aws_iam_user" "github_actions_user" { + name = "github-actions-user" +} + +resource "aws_iam_user_policy" "github_actions_policy" { + name = "github-actions-policy" + user = aws_iam_user.github_actions_user.name + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = ["s3:PutObject", "s3:DeleteObject"], + Resource = "${aws_s3_bucket.wfprev_site_bucket.arn}/*" + }, + { + Effect = "Allow", + Action = "cloudfront:CreateInvalidation", + Resource = "*" + } + ] + }) +} + +resource "aws_iam_role" "github_actions_role" { + name = "github-actions-role" + assume_role_policy = data.aws_iam_user_policy.github_actions_policy.json +} + diff --git a/terraform/s3.tf b/terraform/s3.tf new file mode 100644 index 000000000..d292afaea --- /dev/null +++ b/terraform/s3.tf @@ -0,0 +1,42 @@ +# Bucket create. Public-read or private? +resource "aws_s3_bucket" "wfprev_site_bucket" { + bucket = "wfnews-${var.TARGET_ENV}-site" + acl = "public-read" + force_destroy = true + + website { + index_document = "index.html" + error_document = "index.html" + } +} + +# Uploading assets. This shouldn't be needed because we'll push them up from the +# github action, vs having terraform fetch them +#resource "aws_s3_object" "upload-assets" { +# for_each = fileset("${var.web-assets-path}", "**/*") +# bucket = aws_s3_bucket.wfprev_site_bucket.bucket +# key = each.value +# source = "${var.web-assets-path}/${each.value}" +# content_type = lookup(var.mime_types, regex("\\.[^.]+$", each.value), "application/octet-stream") +#} + +# S3 Bucket Policy for public access +resource "aws_s3_bucket_policy" "wfprev_site_bucket_policy" { + bucket = aws_s3_bucket.wfprev_site_bucket.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = "*", + Action = "s3:GetObject", + Resource = "${aws_s3_bucket.wfprev_site_bucket.arn}/*" + } + ] + }) +} + +output "s3_bucket_name" { + value = aws_s3_bucket.wfprev_site_bucket.bucket +} diff --git a/terraform/security.tf b/terraform/security.tf index 74eef8e61..a46e128b5 100644 --- a/terraform/security.tf +++ b/terraform/security.tf @@ -10,13 +10,13 @@ data "aws_security_group" "data" { name = "Data_sg" } resource "aws_security_group" "wfprev_tomcat_access" { - name = "wfprev-ecs-tasks-allow-access" + name = "wfprev-ecs-tasks-allow-access" description = "Explicitly allow traffic on ports used by WFPREV" - vpc_id = module.network.aws_vpc.id + vpc_id = module.network.aws_vpc.id ingress { - protocol = "tcp" - from_port = var.WFPREV_CLIENT_PORT - to_port = var.WFPREV_API_PORT + protocol = "tcp" + from_port = var.WFPREV_API_PORT + to_port = var.WFPREV_API_PORT cidr_blocks = ["0.0.0.0/0"] } } @@ -26,16 +26,16 @@ resource "aws_security_group" "jumphost" { description = "Allow access to jumphost via ssm" vpc_id = module.network.aws_vpc.id ingress { - protocol = "tcp" - from_port = 3389 - to_port = 3389 + protocol = "tcp" + from_port = 3389 + to_port = 3389 security_groups = [data.aws_security_group.web.id] } ingress { - protocol = "tcp" - from_port = 3389 - to_port = 3389 + protocol = "tcp" + from_port = 3389 + to_port = 3389 security_groups = [data.aws_security_group.app.id] } -} \ No newline at end of file +} diff --git a/terraform/terragrunt.hcl b/terraform/terragrunt.hcl index d67304c0b..da00df53e 100644 --- a/terraform/terragrunt.hcl +++ b/terraform/terragrunt.hcl @@ -34,8 +34,6 @@ WFPREV_API_IMAGE = "${get_env("WFPREV_API_IMAGE")}" WFPREV_API_CPU_UNITS = "${get_env("WFPREV_API_CPU_UNITS")}" WFPREV_API_MEMORY = "${get_env("WFPREV_API_MEMORY")}" WFPREV_API_PORT = "${get_env("WFPREV_API_PORT")}" -WFPREV_CLIENT_ID = "${get_env("WFPREV_CLIENT_ID")}" -WFPREV_CLIENT_SECRET = "${get_env("WFPREV_CLIENT_SECRET")}" WEBADE_OAUTH2_CHECK_TOKEN_URL = "${get_env("WEBADE_OAUTH2_CHECK_TOKEN_URL")}" WEBADE_OAUTH2_CHECK_AUTHORIZE_URL = "${get_env("WEBADE_OAUTH2_CHECK_AUTHORIZE_URL")}" WFPREV_DATASOURCE_URL = "${get_env("WFPREV_DATASOURCE_URL")}" diff --git a/terraform/variables.tf b/terraform/variables.tf index f4475915d..22dc032ea 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -5,18 +5,6 @@ variable "common_tags" { } } -variable "WFPREV_CLIENT_CPU_UNITS" { - description = "client instance CPU units to provision (1 vCPU = 1024 CPU units)" - type = number - default = 512 -} - -variable "WFPREV_CLIENT_MEMORY" { - description = "client instance memory to provision (in MiB)" - type = number - default = 1024 -} - variable "WFPREV_API_CPU_UNITS" { description = "server CPU units to provision (1 vCPU = 1024 CPU units)" type = number @@ -89,16 +77,6 @@ variable "WEBADE_OAUTH2_CHECK_AUTHORIZE_URL" { default = "" } -variable "WFPREV_CLIENT_ID" { - type = string - default = "" -} - -variable "WFPREV_CLIENT_SECRET" { - type = string - default = "" -} - variable "WFPREV_DATASOURCE_USERNAME" { type = string default = "" @@ -107,24 +85,24 @@ variable "WFPREV_DATASOURCE_USERNAME" { variable "WFPREV_DATASOURCE_PASSWORD" { description = "db password, passed in as env variable at runtime" type = string - default = "" + default = "" } variable "WFPREV_USERNAME" { - type = string + type = string default = "" } variable "DB_PASS" { description = "db password, passed in as env variable at runtime" type = string - default = "" + default = "" } variable "api_key" { description = "value for api key" type = string - default = "" + default = "" } variable "server_name" { @@ -148,18 +126,13 @@ variable "ALB_NAME" { variable "TARGET_ENV" { description = "AWS workload account env (e.g. dev, test, prod, sandbox, unclass)" type = string - default = "" + default = "" } variable "WFPREV_API_PORT" { description = "Port exposed by the docker image to redirect traffic to" - type = number - default = 8080 -} - -variable "WFPREV_CLIENT_PORT" { - type = number - default = 8080 + type = number + default = 8080 } variable "WEBADE_OAUTH2_WFPREV_UI_CLIENT_SECRET" { @@ -196,7 +169,7 @@ variable "gov_api_url" { } variable "TARGET_AWS_ACCOUNT_ID" { - type = string + type = string description = "Numerical AWS account ID" } @@ -209,7 +182,7 @@ variable "DB_POSTGRES_VERSION" { variable "DB_INSTANCE_TYPE" { description = "Instance type to use for database vm" type = string - default = "" + default = "" } variable "DB_MULTI_AZ" { @@ -226,23 +199,23 @@ variable "DB_SIZE" { variable "PREVENTION_WAR_NAMES" { - type = list(string) + type = list(string) description = "List of paths to point at payroll API" - default = ["wfprev", "wfprev/*"] + default = ["wfprev", "wfprev/*"] } variable "PREVENTION_API_NAMES" { - type = list(string) + type = list(string) description = "List of paths to point at payroll API" - default = ["wfprev-api", "wfprev-api/*"] + default = ["wfprev-api", "wfprev-api/*"] } //liquibase variable "LIQUIBASE_MEMORY" { description = "Amount of memory to allocate to liquibase instances, in MB" - type = number - default = 512 + type = number + default = 512 } variable "LIQUIBASE_CONTAINER_NAME" { @@ -259,8 +232,8 @@ variable "LIQUIBASE_IMAGE" { variable "LIQUIBASE_CPU" { description = "number of milliCPUs to allocate to liquibase instances" - type = number - default = 256 + type = number + default = 256 } variable "DB_PORT" { @@ -286,12 +259,12 @@ variable "CLOUDFRONT_HEADER" { } variable "NONPROXY_COUNT" { - type = number + type = number default = 1 } variable "CHANGELOG_NAME" { - type = string + type = string default = "main-changelog" } @@ -312,6 +285,40 @@ variable "TARGET_LIQUIBASE_TAG" { } variable "COMMAND" { - type = string + type = string default = "update" -} \ No newline at end of file +} + +# Used if Terraform is uploading data externally into the Site s3 bucket +variable "MIME_TYPES" { + description = "Map of file extensions to MIME types" + type = map(string) + default = { + ".html" = "text/html" + ".css" = "text/css" + ".png" = "image/png" + ".jpg" = "image/jpeg" + ".jpeg" = "image/jpeg" + ".pdf" = "application/pdf" + ".json" = "application/json" + ".js" = "application/javascript" + ".gif" = "image/gif" + ".svg" = "image/svg+xml" + ".ico" = "image/x-icon" + ".txt" = "text/plain" + ".xml" = "application/xml" + ".mp4" = "video/mp4" + ".webm" = "video/webm" + ".webp" = "image/webp" + ".zip" = "application/zip" + ".doc" = "application/msword" + ".docx" = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ".xls" = "application/vnd.ms-excel" + ".xlsx" = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ".csv" = "text/csv" + ".ttf" = "font/ttf" + ".md" = "text/markdown" + ".yaml" = "application/yaml" + ".yml" = "application/yaml" + } +}