diff --git a/cdn/README.md b/cdn/README.md index 8d8ea8c..26748e1 100644 --- a/cdn/README.md +++ b/cdn/README.md @@ -4,6 +4,12 @@ Terraform configuration for the Fastly services of ruby-lang.org. Each service l The `ftp` service has been dormant since 2018 and is kept with `activate = false`. `ftp.ruby-lang.org` is actually served by the `cache` service. +The `logs.rubyci.org` service fronts the public `rubyci` S3 bucket (chkbuild logs). Switching rubyci.org log links over to it happens in the ruby/rubyci repository. + +Bootstrapping a domain on a new TLS subscription takes three steps, because the ACME challenge has to resolve before Fastly will issue: `terraform apply`, then add the challenge CNAME from `terraform output logs_rubyci_managed_dns_challenges` to `dns/dnsconfig.js` and push it, then add the domain CNAME once the subscription reaches `issued`. The challenge record stays in `dnsconfig.js` afterwards, since renewal reuses it. + +Two things about the Fastly API token. It needs the TLS management permission, which is only settable when the automation token is created; without it `fastly_tls_subscription` fails with a bare `403 - Forbidden`. And this account has no default TLS configuration, so `configuration_id` is required, which also decides the CNAME target the domain points at. + ## Usage Credentials come from `~/.config/fastly/token.sh`, which must export `FASTLY_API_KEY` (global scope token), `TF_VAR_datadog_token`, `TF_VAR_logging_s3_access_key`, and `TF_VAR_logging_s3_secret_key`. The state backend also needs AWS credentials that can read and write `s3://ruby-lang-terraform-state`, supplied through any standard AWS mechanism such as `AWS_PROFILE` or `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`. diff --git a/cdn/logs_rubyci.tf b/cdn/logs_rubyci.tf new file mode 100644 index 0000000..eb2bf7e --- /dev/null +++ b/cdn/logs_rubyci.tf @@ -0,0 +1,77 @@ +# Serves chkbuild logs from the public rubyci S3 bucket (ap-northeast-1) +# so that rubyci.org can link to logs.rubyci.org instead of S3 directly. +resource "fastly_service_vcl" "logs_rubyci" { + activate = true + stage = false + default_ttl = 300 + http3 = true + name = "logs.rubyci.org" + stale_if_error = true + stale_if_error_ttl = 43200 + + backend { + address = "rubyci.s3.ap-northeast-1.amazonaws.com" + auto_loadbalance = false + between_bytes_timeout = 10000 + connect_timeout = 1000 + error_threshold = 0 + first_byte_timeout = 15000 + keepalive_time = 0 + max_conn = 200 + max_lifetime = 0 + max_use = 0 + name = "rubyci S3 bucket" + override_host = "rubyci.s3.ap-northeast-1.amazonaws.com" + port = 443 + prefer_ipv6 = false + shield = "tyo-tokyo-jp" + ssl_cert_hostname = "rubyci.s3.ap-northeast-1.amazonaws.com" + ssl_check_cert = true + ssl_sni_hostname = "rubyci.s3.ap-northeast-1.amazonaws.com" + use_ssl = true + weight = 100 + } + + domain { + name = "logs.rubyci.org" + } + + logging_datadog { + format = file("${path.module}/logging/datadog_format.json") + format_version = 2 + name = "Datadog" + processing_region = "none" + region = "AP1" + token = var.datadog_token + } + + request_setting { + bypass_busy_wait = false + force_miss = false + force_ssl = true + max_stale_age = 0 + name = "Force TLS" + timer_support = false + xff = "append" + } + + vcl { + content = file("${path.module}/vcl/logs_rubyci.vcl") + main = true + name = "default" + } +} + +resource "fastly_tls_subscription" "logs_rubyci" { + domains = [one([for d in fastly_service_vcl.logs_rubyci.domain : d.name])] + certificate_authority = "certainly" + # "HTTP/3 & TLS v1.3". The account has no default TLS configuration, and the + # older v1.2 ones used by ruby-lang.org do not advertise http/3. + configuration_id = "cyAx0f8WAbapyNAT4TVOgw" +} + +# CNAME records to add in dns/dnsconfig.js: the ACME challenge for issuance, +# then the domain itself pointing at the Fastly TLS endpoint. +output "logs_rubyci_managed_dns_challenges" { + value = fastly_tls_subscription.logs_rubyci.managed_dns_challenges +} diff --git a/cdn/vcl/logs_rubyci.vcl b/cdn/vcl/logs_rubyci.vcl new file mode 100644 index 0000000..cd06b61 --- /dev/null +++ b/cdn/vcl/logs_rubyci.vcl @@ -0,0 +1,56 @@ +sub vcl_recv { +#FASTLY recv + + if (req.request == "FASTLYPURGE") { + set req.http.Fastly-Purge-Requires-Auth = "1"; + } + + if (req.request != "HEAD" && req.request != "GET" && req.request != "FASTLYPURGE") { + return(pass); + } + + return(lookup); +} + +sub vcl_fetch { +#FASTLY fetch + + set beresp.stale_while_revalidate = 60s; + + if (req.url ~ "/log/\d{8}T\d{6}Z\.") { + # Timestamped chkbuild logs are immutable + set beresp.ttl = 31536000s; + } else { + # cur/, summary pages and other keys are overwritten by chkbuild + set beresp.ttl = 300s; + } + + return(deliver); +} + +sub vcl_hit { +#FASTLY hit + + if (!obj.cacheable) { + return(pass); + } + return(deliver); +} + +sub vcl_miss { +#FASTLY miss + return(fetch); +} + +sub vcl_deliver { +#FASTLY deliver + return(deliver); +} + +sub vcl_error { +#FASTLY error +} + +sub vcl_pass { +#FASTLY pass +} diff --git a/dns/dnsconfig.js b/dns/dnsconfig.js index 583c8ba..ed38ce7 100644 --- a/dns/dnsconfig.js +++ b/dns/dnsconfig.js @@ -33,5 +33,10 @@ D("rubyci.org", REG_CHANGEME, // Cloudflare flattens this apex CNAME; dnscontrol expresses it as ALIAS. ALIAS("@", "rubyci.org.herokudns.com."), CNAME("www", "www.rubyci.org.herokudns.com."), + // chkbuild logs, served by the logs.rubyci.org Fastly service in cdn/. + // The _acme-challenge record renews the Certainly certificate; removing it + // breaks renewal. + CNAME("logs", "m.sni.global.fastly.net."), + CNAME("_acme-challenge.logs", "d867b4erl69vx2y93y.fastly-validations.com."), );