From 41dba94a834b11225706eda0ee264a0d85b9a6cd Mon Sep 17 00:00:00 2001 From: Julien Cristau Date: Wed, 22 Jul 2026 09:37:15 +0200 Subject: [PATCH] feat(fastly): make the default ttl configurable Instead of blanket caching objects for an hour if the origin didn't send cache headers, let the user specify a fallback, similar to https://www.fastly.com/documentation/guides/full-site-delivery/caching/controlling-caching/#setting-a-fallback-ttl --- google_fastly_waf/README.md | 1 + google_fastly_waf/main.tf | 1 + google_fastly_waf/variables.tf | 11 +++++++++++ google_fastly_waf/vcl/main.vcl.tftpl | 2 +- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/google_fastly_waf/README.md b/google_fastly_waf/README.md index 18b7fa3c..46e1c64e 100644 --- a/google_fastly_waf/README.md +++ b/google_fastly_waf/README.md @@ -149,6 +149,7 @@ module "fastly_stage" { | [conditions](#input\_conditions) | List of Fastly conditions to create (REQUEST, RESPONSE or CACHE). |
list(object({
name = string # required, unique
statement = string # VCL conditional expression
type = string # one of: REQUEST, RESPONSE, CACHE
priority = optional(number) # lower runs first, default 10
}))
| `[]` | no | | [ddos\_protection](#input\_ddos\_protection) | Optional DDoS Protection configuration for the Fastly service product enablement. |
object({
enabled = bool
mode = string
})
| `null` | no | | [ddos\_protection\_alert](#input\_ddos\_protection\_alert) | Optional Slack alerting for Fastly DDoS Protection. When set, the module creates a Slack `fastly_integration` and a `fastly_alert` on the `ddos_protection_requests_detect_count` stats metric that notifies the channel behind the webhook. Intended to be paired with `ddos_protection` being enabled. Set to `null` (the default) to create no alerting resources. |
object({
enabled = optional(bool, true)
slack_webhook_secret = string
threshold = optional(number, 1)
period = optional(string, "5m")
description = optional(string)
})
| `null` | no | +| [default\_object\_ttl](#input\_default\_object\_ttl) | Default TTL (in seconds) applied in vcl\_fetch to cacheable responses that arrive from origin without any cache headers (no Expires, Surrogate-Control max-age, or Cache-Control s-maxage/max-age). Lower it to cap how long header-less responses are cached. | `number` | `3600` | no | | [domains](#input\_domains) | A list of domains | `list(any)` | `[]` | no | | [extra\_log\_fields](#input\_extra\_log\_fields) | Extra columns to add to the BigQuery logs table, on top of the base schema in logging/bq\_schema.json. Each entry adds both the log-format field and the matching table column, so the two cannot drift. `expression` is a bare Fastly VCL expression -- no `%{}V` wrapper and no `%%` escaping. The module always wraps it in `json.escape()` and always emits a quoted `STRING` / `NULLABLE` column, so a value should never break the JSON log line. Nesting works, e.g. `if(req.http.X-Foo, req.http.X-Foo, "none")`. There is no type knob: cast in SQL if you need a number (the base schema already stores `response\_status` as `STRING`). Append-only. BigQuery cannot reorder or drop columns, so add new entries at the end of the list and never remove one -- to retire a field, stop populating it and leave the column. This writes into the shared WAF log dataset, which is retained for 90 days and broadly readable. Never log credentials, cookies, authorization headers, or request bodies. |
list(object({
name = string
expression = string
description = optional(string, "")
}))
| `[]` | no | | [https\_redirect\_enabled](#input\_https\_redirect\_enabled) | n/a | `bool` | `true` | no | diff --git a/google_fastly_waf/main.tf b/google_fastly_waf/main.tf index 478aa2bb..53c24ad4 100644 --- a/google_fastly_waf/main.tf +++ b/google_fastly_waf/main.tf @@ -243,6 +243,7 @@ resource "fastly_service_vcl" "default" { environment = var.environment, https_redirect_enabled = var.https_redirect_enabled, cache_header = var.cache_header, + default_object_ttl = var.default_object_ttl, legacy_edge_deployment = var.legacy_edge_deployment } ) diff --git a/google_fastly_waf/variables.tf b/google_fastly_waf/variables.tf index c40e3f4e..175daca6 100644 --- a/google_fastly_waf/variables.tf +++ b/google_fastly_waf/variables.tf @@ -163,6 +163,17 @@ variable "cache_header" { description = "A cache header to check to toggle cache lookup" } +variable "default_object_ttl" { + type = number + default = 3600 + description = <<-EOT + Default TTL (in seconds) applied in vcl_fetch to cacheable responses that + arrive from origin without any cache headers (no Expires, Surrogate-Control + max-age, or Cache-Control s-maxage/max-age). Lower it to cap how long + header-less responses are cached. + EOT +} + variable "bot_management" { description = "Bot Management configuration for the Fastly service product enablement." type = object({ diff --git a/google_fastly_waf/vcl/main.vcl.tftpl b/google_fastly_waf/vcl/main.vcl.tftpl index 4c1b1514..556c9dba 100644 --- a/google_fastly_waf/vcl/main.vcl.tftpl +++ b/google_fastly_waf/vcl/main.vcl.tftpl @@ -91,7 +91,7 @@ sub vcl_fetch { # If no TTL has been provided in the response headers, set a default if (!beresp.http.Expires && !beresp.http.Surrogate-Control ~ "max-age" && !beresp.http.Cache-Control ~ "(?:s-maxage|max-age)") { - set beresp.ttl = 3600s; + set beresp.ttl = ${default_object_ttl}s; # Apply a longer default TTL for images processed using Image Optimizer if (req.http.X-Fastly-Imageopto-Api) {