From e3cbef7fe48efe72797fc96443285057c059ff6d Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Mon, 1 Apr 2024 19:36:47 -0600 Subject: [PATCH] chore(portal): Enable CDN and WAF (#4450) --- terraform/environments/production/portal.tf | 8 +- terraform/environments/staging/portal.tf | 2 + .../modules/google-cloud/apps/elixir/main.tf | 24 +++ .../google-cloud/apps/elixir/network.tf | 163 +++++++++++++++++- .../google-cloud/apps/elixir/variables.tf | 26 +-- 5 files changed, 206 insertions(+), 17 deletions(-) diff --git a/terraform/environments/production/portal.tf b/terraform/environments/production/portal.tf index d8672687e..64b890870 100644 --- a/terraform/environments/production/portal.tf +++ b/terraform/environments/production/portal.tf @@ -420,7 +420,8 @@ module "web" { image = "web" image_tag = var.image_tag - scaling_horizontal_replicas = 2 + scaling_horizontal_replicas = 2 + scaling_max_horizontal_replicas = 4 observability_log_level = "debug" @@ -432,6 +433,8 @@ module "web" { application_dns_tld = "app.${local.tld}" + application_cdn_enabled = true + application_ports = [ { name = "http" @@ -494,7 +497,8 @@ module "api" { image = "api" image_tag = var.image_tag - scaling_horizontal_replicas = 2 + scaling_horizontal_replicas = 2 + scaling_max_horizontal_replicas = 4 observability_log_level = "debug" diff --git a/terraform/environments/staging/portal.tf b/terraform/environments/staging/portal.tf index 5a2c5bf58..cdaa1fedf 100644 --- a/terraform/environments/staging/portal.tf +++ b/terraform/environments/staging/portal.tf @@ -436,6 +436,8 @@ module "web" { application_dns_tld = "app.${local.tld}" + application_cdn_enabled = true + application_ports = [ { name = "http" diff --git a/terraform/modules/google-cloud/apps/elixir/main.tf b/terraform/modules/google-cloud/apps/elixir/main.tf index 063b85712..f468611a6 100644 --- a/terraform/modules/google-cloud/apps/elixir/main.tf +++ b/terraform/modules/google-cloud/apps/elixir/main.tf @@ -287,3 +287,27 @@ resource "google_compute_region_instance_group_manager" "application" { google_compute_instance_template.application ] } + +# Auto-scale instances with high CPU and Memory usage +resource "google_compute_region_autoscaler" "application" { + count = var.scaling_max_horizontal_replicas != null ? 1 : 0 + + project = var.project_id + + name = "${local.application_name}-autoscaler" + + region = var.compute_instance_region + target = google_compute_region_instance_group_manager.application.id + + autoscaling_policy { + max_replicas = var.scaling_max_horizontal_replicas + min_replicas = var.scaling_horizontal_replicas + + # wait 3 minutes before trying to measure the CPU utilization for new instances + cooldown_period = 180 + + cpu_utilization { + target = 0.8 + } + } +} diff --git a/terraform/modules/google-cloud/apps/elixir/network.tf b/terraform/modules/google-cloud/apps/elixir/network.tf index 7e726edc0..1f1c6ff83 100644 --- a/terraform/modules/google-cloud/apps/elixir/network.tf +++ b/terraform/modules/google-cloud/apps/elixir/network.tf @@ -23,7 +23,149 @@ resource "google_compute_security_policy" "default" { type = "CLOUD_ARMOR" + advanced_options_config { + json_parsing = "STANDARD" + log_level = "NORMAL" + } + + adaptive_protection_config { + layer_7_ddos_defense_config { + enable = local.public_application + rule_visibility = "STANDARD" + } + } + rule { + description = "rate limit all requests that match the default rule" + + # TODO: disable preview when we make sure that rate limited logs look good for some time + preview = true + + action = "throttle" + priority = "1" + + match { + versioned_expr = "SRC_IPS_V1" + + config { + src_ip_ranges = ["*"] + } + } + + rate_limit_options { + conform_action = "allow" + exceed_action = "deny(429)" + + enforce_on_key = "IP" + + rate_limit_threshold { + count = 240 + interval_sec = 60 + } + } + } + + rule { + description = "log all requests that match preconfigured sqli-v33-stable OWASP rule" + preview = true + + action = "deny(403)" + priority = "1001" + + match { + expr { + expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})" + } + } + } + + rule { + description = "log all requests that match preconfigured xss-v33-stable OWASP rule" + preview = true + + action = "deny(403)" + priority = "1002" + + match { + expr { + expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})" + } + } + } + + rule { + description = "log all requests that match preconfigured methodenforcement-v33-stable OWASP rule" + preview = true + + action = "deny(403)" + priority = "1003" + + match { + expr { + expression = "evaluatePreconfiguredWaf('methodenforcement-v33-stable', {'sensitivity': 1})" + } + } + } + + rule { + description = "log all requests that match preconfigured scannerdetection-v33-stable OWASP rule" + preview = true + + action = "deny(403)" + priority = "1004" + + match { + expr { + expression = "evaluatePreconfiguredWaf('scannerdetection-v33-stable', {'sensitivity': 1})" + } + } + } + + rule { + description = "log all requests that match preconfigured protocolattack-v33-stable OWASP rule" + preview = true + + action = "deny(403)" + priority = "1005" + + match { + expr { + expression = "evaluatePreconfiguredWaf('protocolattack-v33-stable', {'sensitivity': 1})" + } + } + } + + rule { + description = "log all requests that match preconfigured sessionfixation-v33-stable OWASP rule" + preview = true + + action = "deny(403)" + priority = "1006" + + match { + expr { + expression = "evaluatePreconfiguredWaf('sessionfixation-v33-stable', {'sensitivity': 1})" + } + } + } + + rule { + description = "log all requests that match preconfigured cve-canary GCP rule" + preview = true + + action = "deny(403)" + priority = "1007" + + match { + expr { + expression = "evaluatePreconfiguredWaf('cve-canary', {'sensitivity': 2})" + } + } + } + + rule { + description = "default allow rule" + action = "allow" priority = "2147483647" @@ -34,12 +176,8 @@ resource "google_compute_security_policy" "default" { src_ip_ranges = ["*"] } } - - description = "default allow rule" } - # TODO: Configure more WAF rules - depends_on = [ google_project_service.compute, google_project_service.pubsub, @@ -70,7 +208,22 @@ resource "google_compute_backend_service" "default" { timeout_sec = 86400 connection_draining_timeout_sec = 120 - enable_cdn = false + enable_cdn = var.application_cdn_enabled + + cdn_policy { + cache_mode = "CACHE_ALL_STATIC" + + cache_key_policy { + include_host = true + include_protocol = true + include_query_string = true + } + + default_ttl = 3600 + client_ttl = 3600 + max_ttl = 86400 + } + compression_mode = "DISABLED" custom_request_headers = [ diff --git a/terraform/modules/google-cloud/apps/elixir/variables.tf b/terraform/modules/google-cloud/apps/elixir/variables.tf index 2b7d94a46..bcd4a13b8 100644 --- a/terraform/modules/google-cloud/apps/elixir/variables.tf +++ b/terraform/modules/google-cloud/apps/elixir/variables.tf @@ -47,16 +47,6 @@ variable "container_registry" { description = "Container registry URL to pull the image from." } -# variable "container_registry_api_key" { -# type = string -# nullable = false -# } - -# variable "container_registry_user_name" { -# type = string -# nullable = false -# } - ################################################################################ ## Container Image ################################################################################ @@ -99,6 +89,14 @@ variable "scaling_horizontal_replicas" { description = "Number of replicas in an instance group." } +variable "scaling_max_horizontal_replicas" { + type = number + nullable = true + default = null + + description = "Maximum number of replacias an instance group can be auto-scaled to. `null` disables auto-scaling." +} + ################################################################################ ## Observability ################################################################################ @@ -228,6 +226,14 @@ variable "application_dns_tld" { description = "DNS host which will be used to create DNS records for the application and provision SSL-certificates." } +variable "application_cdn_enabled" { + type = bool + nullable = false + default = false + + description = "Enable CDN for all static assets the application." +} + variable "application_ports" { type = list(object({ name = string