Skip to content

Commit de5504e

Browse files
committed
feat: implement Cloud Armor security policies and configure CDN for Cloud Run deployment
1 parent d03f304 commit de5504e

File tree

9 files changed

+405
-4
lines changed

9 files changed

+405
-4
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Cloud Armor Security Policy
2+
resource "google_compute_security_policy" "security_policy" {
3+
name = "${var.name_prefix}-backend-security-policy"
4+
project = var.project
5+
description = "Backend security policy for ${var.name_prefix}"
6+
type = "CLOUD_ARMOR"
7+
}
8+
9+
# Deny non-GET methods - priority 2147483625
10+
resource "google_compute_security_policy_rule" "deny_non_get" {
11+
security_policy = google_compute_security_policy.security_policy.name
12+
project = var.project
13+
action = "deny(403)"
14+
priority = 2147483625
15+
preview = false
16+
description = "Deny non-GET methods"
17+
18+
match {
19+
expr {
20+
expression = "request.method.upper() != 'GET'"
21+
}
22+
}
23+
}
24+
25+
# Block requests except whitelisted hosts - priority 2147483635
26+
resource "google_compute_security_policy_rule" "block_non_whitelisted_hosts" {
27+
security_policy = google_compute_security_policy.security_policy.name
28+
project = var.project
29+
action = "deny(403)"
30+
priority = 2147483635
31+
preview = false
32+
description = "Block requests except whitelisted hosts"
33+
34+
match {
35+
expr {
36+
expression = "request.headers['host'].lower() != '${var.domain}'"
37+
}
38+
}
39+
}
40+
41+
# Blacklisted user-agents - priority 2147483640
42+
resource "google_compute_security_policy_rule" "block_user_agents" {
43+
security_policy = google_compute_security_policy.security_policy.name
44+
project = var.project
45+
action = "deny(403)"
46+
priority = 2147483640
47+
preview = false
48+
description = "Black-listed user-agents"
49+
50+
match {
51+
expr {
52+
expression = <<-EOT
53+
has(request.headers['user-agent']) && (
54+
request.headers['user-agent'].contains('GenomeCrawler') ||
55+
request.headers['user-agent'].contains('AhrefsBot')
56+
)
57+
EOT
58+
}
59+
}
60+
}
61+
62+
# Default rate limiting rule - priority 2147483646
63+
resource "google_compute_security_policy_rule" "rate_limit" {
64+
security_policy = google_compute_security_policy.security_policy.name
65+
project = var.project
66+
action = "throttle"
67+
priority = 2147483646
68+
preview = false
69+
description = "Default rate limiting rule"
70+
71+
match {
72+
versioned_expr = "SRC_IPS_V1"
73+
config {
74+
src_ip_ranges = ["*"]
75+
}
76+
}
77+
78+
rate_limit_options {
79+
conform_action = "allow"
80+
exceed_action = "deny(403)"
81+
enforce_on_key = "IP"
82+
rate_limit_threshold {
83+
count = 500
84+
interval_sec = 60
85+
}
86+
}
87+
}

terraform/modules/cdn-glb/main.tf

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Serverless Network Endpoint Group (NEG) for Cloud Run
2+
resource "google_compute_region_network_endpoint_group" "serverless_neg" {
3+
name = coalesce(var.neg_name, "${var.name_prefix}-${var.environment}")
4+
region = var.region
5+
project = var.project
6+
network_endpoint_type = "SERVERLESS"
7+
8+
cloud_run {
9+
service = "tech-report-api-prod" # var.cloud_run_service_name
10+
}
11+
}
12+
13+
# Backend Service with CDN
14+
resource "google_compute_backend_service" "backend" {
15+
name = coalesce(var.backend_service_name, var.name_prefix)
16+
project = var.project
17+
compression_mode = "AUTOMATIC"
18+
protocol = "HTTPS"
19+
load_balancing_scheme = "EXTERNAL_MANAGED"
20+
timeout_sec = 30
21+
connection_draining_timeout_sec = 0
22+
locality_lb_policy = "ROUND_ROBIN"
23+
security_policy = google_compute_security_policy.security_policy.id
24+
25+
enable_cdn = var.enable_cdn
26+
27+
dynamic "cdn_policy" {
28+
for_each = var.enable_cdn ? [1] : []
29+
content {
30+
cache_mode = var.cdn_cache_mode
31+
default_ttl = var.cdn_default_ttl
32+
max_ttl = var.cdn_max_ttl
33+
client_ttl = var.cdn_client_ttl
34+
serve_while_stale = var.cdn_serve_while_stale
35+
negative_caching = var.cdn_negative_caching
36+
signed_url_cache_max_age_sec = 0
37+
38+
cache_key_policy {
39+
include_host = false
40+
include_protocol = false
41+
include_query_string = true
42+
}
43+
}
44+
}
45+
46+
backend {
47+
group = google_compute_region_network_endpoint_group.serverless_neg.id
48+
balancing_mode = "UTILIZATION"
49+
capacity_scaler = 1
50+
max_connections = 0
51+
max_connections_per_endpoint = 0
52+
max_connections_per_instance = 0
53+
max_rate = 0
54+
max_rate_per_endpoint = 0
55+
max_rate_per_instance = 0
56+
max_utilization = 0
57+
}
58+
59+
log_config {
60+
enable = true
61+
sample_rate = 1
62+
}
63+
}
64+
65+
# URL Map (Load Balancer)
66+
resource "google_compute_url_map" "url_map" {
67+
name = var.load_balancer_name
68+
project = var.project
69+
default_service = google_compute_backend_service.backend.id
70+
}
71+
72+
# Google-managed SSL Certificate
73+
resource "google_compute_managed_ssl_certificate" "ssl_cert" {
74+
name = coalesce(var.ssl_cert_name, "${var.name_prefix}-ssl-cert")
75+
project = var.project
76+
77+
managed {
78+
domains = [var.domain]
79+
}
80+
}
81+
82+
# HTTPS Target Proxy
83+
resource "google_compute_target_https_proxy" "https_proxy" {
84+
name = coalesce(var.https_proxy_name, "${var.load_balancer_name}-target-proxy")
85+
project = var.project
86+
url_map = google_compute_url_map.url_map.id
87+
ssl_certificates = [google_compute_managed_ssl_certificate.ssl_cert.id]
88+
}
89+
90+
# HTTP Target Proxy (for HTTP to HTTPS redirect)
91+
resource "google_compute_target_http_proxy" "http_proxy" {
92+
name = coalesce(var.http_proxy_name, "${var.load_balancer_name}-http-proxy")
93+
project = var.project
94+
url_map = google_compute_url_map.url_map.id
95+
}
96+
97+
# Global Forwarding Rule for HTTPS
98+
resource "google_compute_global_forwarding_rule" "https_forwarding_rule" {
99+
name = coalesce(var.https_forwarding_rule_name, "${var.load_balancer_name}-https-forwarding-rule")
100+
project = var.project
101+
target = google_compute_target_https_proxy.https_proxy.id
102+
port_range = "443-443"
103+
ip_protocol = "TCP"
104+
ip_address = "35.190.12.254"
105+
ip_version = "IPV4"
106+
source_ip_ranges = []
107+
load_balancing_scheme = "EXTERNAL_MANAGED"
108+
external_managed_backend_bucket_migration_testing_percentage = 0
109+
network_tier = "PREMIUM"
110+
}
111+
112+
# Global Forwarding Rule for HTTP
113+
resource "google_compute_global_forwarding_rule" "http_forwarding_rule" {
114+
name = coalesce(var.http_forwarding_rule_name, "${var.load_balancer_name}-http-forwarding-rule")
115+
project = var.project
116+
target = google_compute_target_http_proxy.http_proxy.id
117+
port_range = "80-80"
118+
ip_protocol = "TCP"
119+
ip_address = "35.190.48.74"
120+
ip_version = "IPV4"
121+
load_balancing_scheme = "EXTERNAL_MANAGED"
122+
external_managed_backend_bucket_migration_testing_percentage = 0
123+
network_tier = "PREMIUM"
124+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
output "load_balancer_ip_http" {
2+
description = "The IP address of the HTTP load balancer"
3+
value = google_compute_global_forwarding_rule.http_forwarding_rule.ip_address
4+
}
5+
6+
output "load_balancer_ip_https" {
7+
description = "The IP address of the HTTPS load balancer"
8+
value = google_compute_global_forwarding_rule.https_forwarding_rule.ip_address
9+
}
10+
11+
output "backend_service_id" {
12+
description = "The ID of the backend service"
13+
value = google_compute_backend_service.backend.id
14+
}
15+
16+
output "url_map_id" {
17+
description = "The ID of the URL map"
18+
value = google_compute_url_map.url_map.id
19+
}
20+
21+
output "ssl_certificate_id" {
22+
description = "The ID of the SSL certificate"
23+
value = google_compute_managed_ssl_certificate.ssl_cert.id
24+
}
25+
26+
output "neg_id" {
27+
description = "The ID of the serverless NEG"
28+
value = google_compute_region_network_endpoint_group.serverless_neg.id
29+
}
30+
31+
output "security_policy_id" {
32+
description = "The ID of the Cloud Armor security policy"
33+
value = google_compute_security_policy.security_policy.id
34+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
variable "project" {
2+
description = "The GCP project ID"
3+
type = string
4+
}
5+
6+
variable "region" {
7+
description = "The GCP region for the Cloud Run service"
8+
type = string
9+
default = "us-central1"
10+
}
11+
variable "environment" {
12+
description = "The 'Environment' that is being created/deployed. Applied as a suffix to many resources."
13+
type = string
14+
}
15+
16+
variable "cloud_run_service_name" {
17+
description = "The name of the Cloud Run service to route traffic to"
18+
type = string
19+
}
20+
21+
variable "domain" {
22+
description = "The domain name for the SSL certificate"
23+
type = string
24+
}
25+
26+
variable "name_prefix" {
27+
description = "Prefix for naming resources"
28+
type = string
29+
default = "report-api"
30+
}
31+
32+
variable "load_balancer_name" {
33+
description = "Name for the load balancer (URL map)"
34+
type = string
35+
}
36+
37+
# CDN Configuration
38+
variable "enable_cdn" {
39+
description = "Whether to enable CDN for the backend service"
40+
type = bool
41+
default = true
42+
}
43+
44+
variable "cdn_cache_mode" {
45+
description = "CDN cache mode (CACHE_ALL_STATIC, USE_ORIGIN_HEADERS, FORCE_CACHE_ALL)"
46+
type = string
47+
default = "CACHE_ALL_STATIC"
48+
}
49+
50+
variable "cdn_default_ttl" {
51+
description = "Default TTL for cached content in seconds"
52+
type = number
53+
default = 2592000 # 30 days
54+
}
55+
56+
variable "cdn_max_ttl" {
57+
description = "Maximum TTL for cached content in seconds"
58+
type = number
59+
default = 2592000 # 30 days
60+
}
61+
62+
variable "cdn_client_ttl" {
63+
description = "Client TTL for cached content in seconds (browser cache)"
64+
type = number
65+
default = 28800 # 8 hours
66+
}
67+
68+
variable "cdn_serve_while_stale" {
69+
description = "Time to serve stale content while revalidating in seconds"
70+
type = number
71+
default = 0
72+
}
73+
74+
variable "cdn_negative_caching" {
75+
description = "Whether to enable negative caching"
76+
type = bool
77+
default = false
78+
}
79+
80+
# Resource naming overrides (for importing existing resources)
81+
variable "neg_name" {
82+
description = "Name for the serverless NEG (overrides name_prefix-neg)"
83+
type = string
84+
default = null
85+
}
86+
87+
variable "backend_service_name" {
88+
description = "Name for the backend service (overrides name_prefix)"
89+
type = string
90+
default = null
91+
}
92+
93+
variable "ssl_cert_name" {
94+
description = "Name for the SSL certificate (overrides name_prefix-ssl-cert)"
95+
type = string
96+
default = null
97+
}
98+
99+
variable "https_proxy_name" {
100+
description = "Name for the HTTPS proxy (overrides load_balancer_name-target-proxy)"
101+
type = string
102+
default = null
103+
}
104+
105+
variable "http_proxy_name" {
106+
description = "Name for the HTTP proxy"
107+
type = string
108+
default = null
109+
}
110+
111+
variable "https_forwarding_rule_name" {
112+
description = "Name for the HTTPS forwarding rule"
113+
type = string
114+
default = null
115+
}
116+
117+
variable "http_forwarding_rule_name" {
118+
description = "Name for the HTTP forwarding rule"
119+
type = string
120+
default = null
121+
}

terraform/modules/run-service/main.tf

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,13 @@ resource "google_cloud_run_v2_service" "service" {
134134

135135
template {
136136
service_account = var.service_account_email
137+
timeout = var.timeout
138+
max_instance_request_concurrency = var.max_instance_request_concurrency
137139

138140
containers {
139141
image = docker_registry_image.registry_image.name
140142
resources {
143+
cpu_idle = var.environment == "prod" ? false : true
141144
limits = {
142145
cpu = var.available_cpu
143146
memory = var.available_memory
@@ -151,10 +154,9 @@ resource "google_cloud_run_v2_service" "service" {
151154
}
152155
}
153156
}
154-
timeout = var.timeout
155-
max_instance_request_concurrency = var.max_instance_request_concurrency
156157
}
157158
scaling {
159+
scaling_mode = "AUTOMATIC"
158160
min_instance_count = var.min_instances
159161
}
160162
traffic {

0 commit comments

Comments
 (0)