# ── Serverless NEGs ─────────────────────────────────────────────────────────── # One NEG per Cloud Run service. NEGs for Cloud Run must use SERVERLESS type # and reference the Cloud Run service by name + region. resource "google_compute_region_network_endpoint_group" "prod_us" { name = "marketing-neg-us" network_endpoint_type = "SERVERLESS" region = "us-central1" project = var.project_id cloud_run { service = google_cloud_run_v2_service.prod_us.name } } resource "google_compute_region_network_endpoint_group" "prod_eu" { name = "marketing-neg-eu" network_endpoint_type = "SERVERLESS" region = "europe-west1" project = var.project_id cloud_run { service = google_cloud_run_v2_service.prod_eu.name } } resource "google_compute_region_network_endpoint_group" "prod_apac" { name = "marketing-neg-apac" network_endpoint_type = "SERVERLESS" region = "asia-northeast1" project = var.project_id cloud_run { service = google_cloud_run_v2_service.prod_apac.name } } # ── Cloud Armor Security Policy ─────────────────────────────────────────────── # Applied to the backend service. Rules evaluated top-down (lowest priority wins). resource "google_compute_security_policy" "marketing" { name = "marketing-armor" project = var.project_id description = "Cloud Armor policy for neurontechnologies.ai marketing site" # ── Rule 1000: SQLi protection ──────────────────────────────────────────── rule { action = "deny(403)" priority = 1000 description = "Block SQLi attacks (OWASP CRS sqli-v33-stable)" match { expr { expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})" } } } # ── Rule 1001: XSS protection ───────────────────────────────────────────── rule { action = "deny(403)" priority = 1001 description = "Block XSS attacks (OWASP CRS xss-v33-stable)" match { expr { expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})" } } } # ── Rule 2000: Rate limiting ─────────────────────────────────────────────── # Throttle any single IP exceeding 1000 req/min (≈17 req/s). rule { action = "throttle" priority = 2000 description = "Rate limit: 1000 req/min per IP" match { versioned_expr = "SRC_IPS_V1" config { src_ip_ranges = ["*"] } } rate_limit_options { conform_action = "allow" exceed_action = "deny(429)" rate_limit_threshold { count = 1000 interval_sec = 60 } enforce_on_key = "IP" } } # ── Default rule: allow all ─────────────────────────────────────────────── rule { action = "allow" priority = 2147483647 description = "Default: allow" match { versioned_expr = "SRC_IPS_V1" config { src_ip_ranges = ["*"] } } } } # ── Prod Backend Service ────────────────────────────────────────────────────── # EXTERNAL_MANAGED scheme is required for Cloud CDN + global LB. # All three regional NEGs are attached with equal weight. resource "google_compute_backend_service" "prod" { name = "marketing-backend-prod" project = var.project_id load_balancing_scheme = "EXTERNAL_MANAGED" protocol = "HTTPS" timeout_sec = 30 # Cloud Armor security_policy = google_compute_security_policy.marketing.self_link # Cloud CDN enable_cdn = true cdn_policy { cache_mode = "CACHE_ALL_STATIC" default_ttl = 3600 max_ttl = 86400 client_ttl = 3600 negative_caching = true serve_while_stale = 86400 cache_key_policy { include_host = true include_protocol = true include_query_string = true } } backend { group = google_compute_region_network_endpoint_group.prod_us.self_link } backend { group = google_compute_region_network_endpoint_group.prod_eu.self_link } backend { group = google_compute_region_network_endpoint_group.prod_apac.self_link } log_config { enable = true sample_rate = 1.0 } } # ── SSL Certificates ────────────────────────────────────────────────────────── # google_compute_managed_ssl_certificate — Google-managed, auto-renewed. # Provisioning requires DNS A records pointing to the LB IP (chicken-and-egg: # apply the Terraform, get the IP from outputs, update DNS, then cert provisions). resource "google_compute_managed_ssl_certificate" "prod" { name = "marketing-cert-prod" project = var.project_id managed { domains = [ "neurontechnologies.ai", "www.neurontechnologies.ai", ] } } # ── Prod URL Map — host-based routing ──────────────────────────────────────── # One global IP handles all three services via Host header routing. # - neurontechnologies.ai / www. → marketing (default) # - api.neurontechnologies.ai → API backend # - accounts.neurontechnologies.ai → accounts backend resource "google_compute_url_map" "prod" { name = "marketing-urlmap-prod" project = var.project_id default_service = google_compute_backend_service.prod.self_link host_rule { hosts = ["neurontechnologies.ai", "www.neurontechnologies.ai"] path_matcher = "marketing" } host_rule { hosts = ["api.neurontechnologies.ai"] path_matcher = "api" } host_rule { hosts = ["accounts.neurontechnologies.ai"] path_matcher = "accounts" } host_rule { hosts = ["neuron.neurontechnologies.ai", "*.neurontechnologies.ai"] path_matcher = "soma" } path_matcher { name = "marketing" default_service = google_compute_backend_service.prod.self_link } path_matcher { name = "api" default_service = google_compute_backend_service.api.self_link } path_matcher { name = "accounts" default_service = google_compute_backend_service.accounts.self_link } path_matcher { name = "soma" default_service = google_compute_backend_service.soma.self_link } } # ── Prod HTTPS Target Proxy ─────────────────────────────────────────────────── resource "google_compute_target_https_proxy" "prod" { name = "marketing-https-proxy-prod" project = var.project_id url_map = google_compute_url_map.prod.self_link ssl_certificates = [ google_compute_managed_ssl_certificate.prod.self_link, google_compute_managed_ssl_certificate.accounts.self_link, google_compute_managed_ssl_certificate.api.self_link, google_compute_managed_ssl_certificate.soma.self_link, ] } # ── Prod HTTP → HTTPS redirect ──────────────────────────────────────────────── # Separate URL map that redirects all HTTP traffic to HTTPS. resource "google_compute_url_map" "prod_http_redirect" { name = "marketing-urlmap-prod-http-redirect" project = var.project_id default_url_redirect { https_redirect = true redirect_response_code = "MOVED_PERMANENTLY_DEFAULT" strip_query = false } } resource "google_compute_target_http_proxy" "prod" { name = "marketing-http-proxy-prod" project = var.project_id url_map = google_compute_url_map.prod_http_redirect.self_link } # ── Prod Global Forwarding Rules ────────────────────────────────────────────── # Both rules share the same global anycast IP. resource "google_compute_global_address" "prod" { name = "marketing-ip-prod" project = var.project_id } resource "google_compute_global_forwarding_rule" "prod_https" { name = "marketing-fwd-prod-https" project = var.project_id target = google_compute_target_https_proxy.prod.self_link ip_address = google_compute_global_address.prod.address port_range = "443" load_balancing_scheme = "EXTERNAL_MANAGED" } resource "google_compute_global_forwarding_rule" "prod_http" { name = "marketing-fwd-prod-http" project = var.project_id target = google_compute_target_http_proxy.prod.self_link ip_address = google_compute_global_address.prod.address port_range = "80" load_balancing_scheme = "EXTERNAL_MANAGED" }