Skip to content

Hardening del CDN per al mapa

El mapa de la web carrega .pmtiles (tiles vectorials via range requests HTTP), glyphs .pbf i un pòster PNG des de cdn.ediacarastudio.com/shared/maps/. Aquesta pàgina recull la configuració de Cloudflare que ha d’existir abans de pujar les dades, o el mapa es trenca (CORS) o cada visita costa lectures d’R2 (sense cache d’edge).

R2 factura operacions de lectura (Class B), no l’egress (la sortida de dades és gratuïta). Una sessió de mapa ≈ 50 range requests / ~1,3 MB. Sense cache d’edge, cada visitant costaria ~50 lectures d’R2 per sempre (10 M/mes gratis, després $0,36/M). Amb la config de sota, les lectures d’R2 cauen a prop de zero independentment del trànsit.

Dos problemes detectats abans de pujar res:

SímptomaCausa
.pmtiles no es cacheja (cf-cache-status: MISS)L’extensió no és a la llista de cache per defecte de Cloudflare
El fetch() del mapa fallaEl bucket no tenia cap política CORS (ediacarastudio.comcdn. és cross-origin)

1. Cache Rule — maps-cache-everything (crític)

Section titled “1. Cache Rule — maps-cache-everything (crític)”

Caching → Cache Rules. Filtre: host cdn.ediacarastudio.com i path que comenci per /shared/maps/. Acció: eligible for cache, Edge TTL i Browser TTL = 1 mes (les dades són immutables; en actualitzar, canvia el nom del fitxer o purga la cache).

2. CORS del bucket (crític — el mapa no funciona sense això)

Section titled “2. CORS del bucket (crític — el mapa no funciona sense això)”

R2 → bucket ediacarastudio-assets → Settings → CORS:

[
{
"AllowedOrigins": [
"https://ediacarastudio.com",
"https://www.ediacarastudio.com",
"http://localhost:3000"
],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["range", "if-match"],
"ExposeHeaders": ["etag", "content-range", "content-length"],
"MaxAgeSeconds": 86400
}
]

localhost:3000 manté el dev local funcionant contra el CDN real. Restringir AllowedOrigins també impedeix que altres webs facin fetch() dels nostres tiles.

Section titled “3. WAF — maps-block-foreign-referer (anti-hotlink)”

Security → WAF → Custom rules. Bloqueja peticions a /shared/maps/ amb un Referer que no contingui ediacarastudio.com (els referers buits es permeten a propòsit: navegadors estrictes amb la privacitat els treuen). És dissuasió, no una frontera de seguretat: el contains és laxa (un ediacarastudio.com.evil.tld passa); el regex requeriria pla de pagament. El sostre de cost real el posen les regles 1 i 4.

Security → WAF → Rate limiting. 150 peticions / 10 s per IP → block 10 s sobre /shared/maps/. Una sessió legítima són ~50 peticions en pocs segons, així que hi ha marge de sobra. ⚠️ El pla gratuït només permet una regla de rate-limit — és aquesta.

Security → Bots → enable. Gratuït, bloqueja bots coneguts a tota la zona.

La fa pnpm sync al repo geodata (scripts/sync.ts): rclone copy (mai sync) limitat a shared/maps/, amb Cache-Control: public, max-age=31536000, immutable. Fa servir copy perquè el bucket és compartit amb els brand assets i sync els esborraria (vegeu Cloudflare → R2).

Verificació (després de configurar + pujar)

Section titled “Verificació (després de configurar + pujar)”
Terminal window
BASE="https://cdn.ediacarastudio.com/shared/maps"
# Cache d'edge: la 2a petició ha de ser HIT
curl -sI "$BASE/camp-tarragona.pmtiles" | grep -i cf-cache-status # MISS, després HIT
# Range requests: 206 amb content-range
curl -sI -r 0-16383 "$BASE/camp-tarragona.pmtiles" | grep -iE "HTTP|content-range"
# CORS: ha de tornar l'origen del site
curl -sI -H "Origin: https://ediacarastudio.com" -r 0-127 \
"$BASE/camp-tarragona.pmtiles" | grep -i access-control-allow-origin
# WAF: referer aliè bloquejat (403)
curl -s -o /dev/null -w "%{http_code}\n" -H "Referer: https://evil.example/" \
"$BASE/camp-tarragona.pmtiles" # 403