diff --git a/.github/workflows/tf-azure-deploy.yml b/.github/workflows/tf-azure-deploy.yml index 1c0a671a8..4d0a7e6d0 100644 --- a/.github/workflows/tf-azure-deploy.yml +++ b/.github/workflows/tf-azure-deploy.yml @@ -32,47 +32,6 @@ env: ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - TF_VAR_environment: ${{ vars.ENVIRONMENT }} - TF_VAR_resource_name_prefix: ${{ vars.RESOURCE_PREFIX }} - TF_VAR_kv_certificate_authority_username: ${{ secrets.CERTIFICATE_AUTHORITY_USERNAME }} - TF_VAR_kv_certificate_authority_password: ${{ secrets.CERTIFICATE_AUTHORITY_PASSWORD }} - TF_VAR_kv_certificate_authority_admin_email: ${{ vars.CERTIFICATE_AUTHORITY_ADMIN_EMAIL }} - TF_VAR_kv_certificate_authority_admin_first_name: ${{ secrets.CERTIFICATE_AUTHORITY_ADMIN_FIRST_NAME }} - TF_VAR_kv_certificate_authority_admin_last_name: ${{ secrets.CERTIFICATE_AUTHORITY_ADMIN_LAST_NAME }} - TF_VAR_kv_certificate_authority_admin_phone_no: ${{ secrets.CERTIFICATE_AUTHORITY_ADMIN_PHONE_NO }} - TF_VAR_kv_certificate_label: ${{ vars.CERTIFICATE_LABEL }} - TF_VAR_kv_certificate_subject: ${{ vars.CERTIFICATE_SUBJECT }} - TF_VAR_psqlfs_sku: ${{ vars.PSQLFS_SKU }} - TF_VAR_psqlfs_storage: ${{ vars.PSQLFS_STORAGE }} - TF_VAR_psqlfs_username: ${{ secrets.PSQLFS_USERNAME }} - TF_VAR_psqlfs_password: ${{ secrets.PSQLFS_PASSWORD }} - TF_VAR_psqlfs_geo_redundant_backup: ${{ vars.PSQLFS_GEO_REDUNDANT_BACKUP }} - TF_VAR_psqlfs_ha_enabled: ${{ vars.PSQLFS_HA_ENABLED }} - TF_VAR_asp_sku: ${{ vars.ASP_SKU }} - TF_VAR_webapp_worker_count: ${{ vars.WEBAPP_WORKER_COUNT }} - TF_VAR_webapp_name: ${{ vars.WEBAPP_NAME }} - TF_VAR_workerapp_name: ${{ vars.WORKERAPP_NAME }} - TF_VAR_reviewapp_name: ${{ vars.REVIEWAPP_NAME }} - TF_VAR_webapp_database_url: ${{ secrets.WEBAPP_DATABASE_URL }} - TF_VAR_webapp_docker_registry_url: https://ghcr.io - TF_VAR_webapp_docker_image: dfe-digital/early-years-foundation-recovery - TF_VAR_webapp_docker_image_tag: latest - TF_VAR_custom_domain_name: ${{ vars.CUSTOM_DOMAIN }} - TF_VAR_webapp_config_bot_token: ${{ secrets.WEBAPP_CONFIG_BOT_TOKEN }} - TF_VAR_webapp_config_contentful_environment: ${{ vars.WEBAPP_CONFIG_CONTENTFUL_ENVIRONMENT }} - TF_VAR_webapp_config_contentful_preview: ${{ vars.WEBAPP_CONFIG_CONTENTFUL_PREVIEW }} - TF_VAR_webapp_config_domain: ${{ vars.WEBAPP_CONFIG_DOMAIN }} - TF_VAR_webapp_config_editor: ${{ vars.WEBAPP_CONFIG_EDITOR }} - TF_VAR_webapp_config_feedback_url: ${{ vars.WEBAPP_CONFIG_FEEDBACK_URL }} - TF_VAR_webapp_config_grover_no_sandbox: ${{ vars.WEBAPP_CONFIG_GROVER_NO_SANDBOX }} - TF_VAR_webapp_config_google_cloud_bucket: ${{ vars.WEBAPP_CONFIG_GOOGLE_CLOUD_BUCKET }} - TF_VAR_webapp_config_node_env: ${{ vars.WEBAPP_CONFIG_NODE_ENV }} - TF_VAR_webapp_config_rails_env: ${{ vars.WEBAPP_CONFIG_RAILS_ENV }} - TF_VAR_webapp_config_rails_log_to_stdout: ${{ vars.WEBAPP_CONFIG_RAILS_LOG_TO_STDOUT }} - TF_VAR_webapp_config_rails_master_key: ${{ secrets.WEBAPP_CONFIG_RAILS_MASTER_KEY }} - TF_VAR_webapp_config_rails_max_threads: ${{ vars.WEBAPP_CONFIG_RAILS_MAX_THREADS }} - TF_VAR_webapp_config_rails_serve_static_files: ${{ vars.WEBAPP_CONFIG_RAILS_SERVE_STATIC_FILES }} - TF_VAR_webapp_config_web_concurrency: ${{ vars.WEBAPP_CONFIG_WEB_CONCURRENCY }} jobs: terraform-plan: @@ -107,6 +66,16 @@ jobs: - name: Terraform Format run: terraform fmt -check + # Generates Terraform variable files + - name: Terraform Variables + shell: bash + env: + WEB_SECRETS: ${{ toJSON(secrets) }} + WEB_VARS: ${{ toJSON(vars) } + run: | + printf '%s\n' "${WEB_SECRETS,,}" > web-secrets.auto.tfvars.json + printf '%s\n' "${WEB_VARS,,}" > web-vars.auto.tfvars.json + # Generates an execution plan for Terraform # An exit code of 0 indicated no changes, 1 a terraform failure, 2 there are pending changes. - name: Terraform Plan @@ -180,6 +149,16 @@ jobs: -backend-config="container_name=${{ secrets.TERRAFORM_STATE_STORAGE_CONTAINER_NAME }}" -backend-config="key=${{ secrets.TERRAFORM_STATE_KEY }}" + # Generates Terraform variable files + - name: Terraform Variables + shell: bash + env: + WEB_SECRETS: ${{ toJSON(secrets) }} + WEB_VARS: ${{ toJSON(vars) } + run: | + printf '%s\n' "${WEB_SECRETS,,}" > web-secrets.auto.tfvars.json + printf '%s\n' "${WEB_VARS,,}" > web-vars.auto.tfvars.json + # Download saved plan from artifacts - name: Download Terraform Plan uses: actions/download-artifact@v3 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 21327395b..f68d95799 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -27,4 +27,14 @@ fi bundle exec rails db:prepare assets:precompile +if [ -z ${ENVIRONMENT} ] +then + echo "ENVIRONMENT is not defined so development database may not contain seed data" +else + if [ !${ENVIRONMENT}=="development" ] + then + bundle exec rails db:seed + fi +fi + exec bundle exec "$@" diff --git a/terraform-azure/local.tf b/terraform-azure/local.tf index 3e384b441..b8d9432ef 100644 --- a/terraform-azure/local.tf +++ b/terraform-azure/local.tf @@ -12,6 +12,7 @@ locals { # Web Application Configuration webapp_app_settings = { + "ENVIRONMENT" = var.environment "DATABASE_URL" = var.webapp_database_url "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false" "GOVUK_APP_DOMAIN" = "london.cloudapps.digital" #TODO: Remove this dependency post-migration to Azure @@ -47,6 +48,7 @@ locals { # Review Application Configuration reviewapp_app_settings = { + "ENVIRONMENT" = var.environment "DATABASE_URL" = var.webapp_database_url "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false" "GOVUK_APP_DOMAIN" = "london.cloudapps.digital" #TODO: Remove this dependency post-migration to Azure diff --git a/terraform-azure/main.tf b/terraform-azure/main.tf index aadbe1fd7..24e1c454e 100644 --- a/terraform-azure/main.tf +++ b/terraform-azure/main.tf @@ -38,7 +38,7 @@ module "network" { kv_certificate_authority_name = "GlobalSign" kv_certificate_authority_username = var.kv_certificate_authority_username kv_certificate_authority_password = var.kv_certificate_authority_password - kv_certificate_authority_admin_email = var.kv_certificate_authority_admin_email + kv_certificate_authority_admin_email = var.admin_email_address kv_certificate_authority_admin_first_name = var.kv_certificate_authority_admin_first_name kv_certificate_authority_admin_last_name = var.kv_certificate_authority_admin_last_name kv_certificate_authority_admin_phone_no = var.kv_certificate_authority_admin_phone_no @@ -69,11 +69,12 @@ module "webapp" { source = "./terraform-azure-web" environment = var.environment - asp_sku = var.asp_sku - webapp_worker_count = var.webapp_worker_count location = var.azure_region resource_group = azurerm_resource_group.rg.name resource_name_prefix = var.resource_name_prefix + asp_sku = var.asp_sku + webapp_admin_email_address = var.admin_email_address + webapp_worker_count = var.webapp_worker_count webapp_subnet_id = module.network.webapp_subnet_id webapp_name = var.webapp_name webapp_app_settings = local.webapp_app_settings @@ -81,7 +82,7 @@ module "webapp" { webapp_docker_image_tag = var.webapp_docker_image_tag webapp_docker_registry_url = var.webapp_docker_registry_url webapp_session_cookie_name = "_early_years_foundation_recovery_session" - webapp_custom_domain_name = var.webapp_custom_domain_name + webapp_custom_domain_name = var.custom_domain_name webapp_custom_domain_cert_secret_label = var.kv_certificate_label webapp_health_check_path = "/health" webapp_health_check_eviction_time_in_min = 10 @@ -117,10 +118,10 @@ module "review-apps" { # Review Applications are only deployed to the Development subscription count = var.environment == "development" ? 1 : 0 - asp_sku = "P1v2" location = var.azure_region resource_group = azurerm_resource_group.rg.name resource_name_prefix = "${var.resource_name_prefix}-review" + asp_sku = "P1v2" webapp_vnet_name = module.network.vnet_name webapp_name = var.reviewapp_name webapp_app_settings = local.reviewapp_app_settings diff --git a/terraform-azure/terraform-azure-web/appgateway.tf b/terraform-azure/terraform-azure-web/appgateway.tf index aa3a56258..d12ac59c2 100644 --- a/terraform-azure/terraform-azure-web/appgateway.tf +++ b/terraform-azure/terraform-azure-web/appgateway.tf @@ -22,16 +22,6 @@ resource "azurerm_web_application_firewall_policy" "agw_wafp" { match_variable = "RequestCookieNames" selector = var.webapp_session_cookie_name selector_match_operator = "Equals" - - excluded_rule_set { - type = "OWASP" - version = "3.2" - - rule_group { - rule_group_name = "REQUEST-942-APPLICATION-ATTACK-SQLI" - excluded_rules = [942440] - } - } } } diff --git a/terraform-azure/terraform-azure-web/variables.tf b/terraform-azure/terraform-azure-web/variables.tf index 3a30c73e8..a8bb028eb 100644 --- a/terraform-azure/terraform-azure-web/variables.tf +++ b/terraform-azure/terraform-azure-web/variables.tf @@ -23,6 +23,12 @@ variable "asp_sku" { type = string } +variable "webapp_admin_email_address" { + description = "Email Address of the Admin" + type = string + sensitive = true +} + variable "webapp_worker_count" { description = "Number of Workers for the App Service Plan" type = string diff --git a/terraform-azure/terraform-azure-web/webapp.tf b/terraform-azure/terraform-azure-web/webapp.tf index 0a15f6592..d02694642 100644 --- a/terraform-azure/terraform-azure-web/webapp.tf +++ b/terraform-azure/terraform-azure-web/webapp.tf @@ -202,6 +202,123 @@ resource "azurerm_monitor_diagnostic_setting" "webapp_slot_logs_monitor" { } } +# Configure Web App Autoscaling +resource "azurerm_monitor_autoscale_setting" "asp_as" { + # Autoscaling rules only deployed to the Test and Production subscription + count = var.environment != "development" ? 1 : 0 + + name = "${var.resource_name_prefix}-asp-as" + location = var.location + resource_group_name = var.resource_group + target_resource_id = azurerm_service_plan.asp.id + + profile { + name = "Autoscaling conditions" + + capacity { + default = 2 + minimum = 2 + maximum = 5 + } + + rule { + metric_trigger { + metric_name = "CpuPercentage" + metric_namespace = "microsoft.web/serverfarms" + metric_resource_id = azurerm_service_plan.asp.id + statistic = "Average" + operator = "GreaterThan" + threshold = 75 + time_aggregation = "Average" + time_grain = "PT1M" + time_window = "PT10M" + } + + scale_action { + direction = "Increase" + type = "ChangeCount" + value = "1" + cooldown = "PT5M" + } + } + + rule { + metric_trigger { + metric_name = "CpuPercentage" + metric_namespace = "microsoft.web/serverfarms" + metric_resource_id = azurerm_service_plan.asp.id + statistic = "Average" + operator = "LessThan" + threshold = 20 + time_aggregation = "Average" + time_grain = "PT1M" + time_window = "PT10M" + } + + scale_action { + direction = "Decrease" + type = "ChangeCount" + value = "1" + cooldown = "PT5M" + } + } + + rule { + metric_trigger { + metric_name = "MemoryPercentage" + metric_namespace = "microsoft.web/serverfarms" + metric_resource_id = azurerm_service_plan.asp.id + statistic = "Average" + operator = "GreaterThan" + threshold = 70 + time_aggregation = "Average" + time_grain = "PT1M" + time_window = "PT10M" + } + + scale_action { + direction = "Increase" + type = "ChangeCount" + value = "1" + cooldown = "PT5M" + } + } + + rule { + metric_trigger { + metric_name = "MemoryPercentage" + metric_namespace = "microsoft.web/serverfarms" + metric_resource_id = azurerm_service_plan.asp.id + statistic = "Average" + operator = "LessThan" + threshold = 50 + time_aggregation = "Average" + time_grain = "PT1M" + time_window = "PT10M" + } + + scale_action { + direction = "Decrease" + type = "ChangeCount" + value = "1" + cooldown = "PT5M" + } + } + } + + notification { + email { + send_to_subscription_administrator = true + send_to_subscription_co_administrator = true + custom_emails = [var.webapp_admin_email_address] + } + } + + lifecycle { + ignore_changes = [tags] + } +} + # Create Custom Domain Name resource "azurerm_app_service_custom_hostname_binding" "webapp_custom_domain" { # Custom hostname only deployed to the Test and Production subscription diff --git a/terraform-azure/variables.tf b/terraform-azure/variables.tf index 9f7f97d77..e789ad619 100644 --- a/terraform-azure/variables.tf +++ b/terraform-azure/variables.tf @@ -16,20 +16,20 @@ variable "resource_name_prefix" { type = string } -variable "kv_certificate_authority_username" { - description = "Username for the Certificate provider" +variable "admin_email_address" { + description = "Email Address of the Admin" type = string sensitive = true } -variable "kv_certificate_authority_password" { - description = "Password the Certificate provider" +variable "kv_certificate_authority_username" { + description = "Username for the Certificate provider" type = string sensitive = true } -variable "kv_certificate_authority_admin_email" { - description = "Email Address of the Certificate Authority Admin" +variable "kv_certificate_authority_password" { + description = "Password the Certificate provider" type = string sensitive = true } @@ -147,7 +147,7 @@ variable "webapp_docker_image_tag" { type = string } -variable "webapp_custom_domain_name" { +variable "custom_domain_name" { description = "Custom domain hostname" type = string } @@ -210,10 +210,6 @@ variable "webapp_config_rails_serve_static_files" { type = bool } -variable "webapp_config_training_modules" { - type = string -} - variable "webapp_config_web_concurrency" { type = string } \ No newline at end of file