Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cdn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
77 changes: 77 additions & 0 deletions cdn/logs_rubyci.tf
Original file line number Diff line number Diff line change
@@ -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
}
56 changes: 56 additions & 0 deletions cdn/vcl/logs_rubyci.vcl
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions dns/dnsconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
);