diff --git a/terraform/cache-bucket/main.tf b/terraform/cache-bucket/main.tf new file mode 100644 index 00000000..3ca98d53 --- /dev/null +++ b/terraform/cache-bucket/main.tf @@ -0,0 +1,113 @@ +variable "bucket_name" { + type = string +} + +resource "aws_s3_bucket" "cache" { + provider = aws + bucket = var.bucket_name + + lifecycle_rule { + enabled = true + + transition { + days = 365 + storage_class = "STANDARD_IA" + } + } + + cors_rule { + allowed_headers = ["Authorization"] + allowed_methods = ["GET"] + allowed_origins = ["*"] + max_age_seconds = 3000 + } +} + +resource "aws_s3_bucket_object" "cache-nix-cache-info" { + provider = aws + + acl = "public-read" + bucket = aws_s3_bucket.cache.bucket + content_type = "text/x-nix-cache-info" + etag = filemd5("${path.module}/../cache-staging/nix-cache-info") + key = "nix-cache-info" + source = "${path.module}/../cache-staging/nix-cache-info" +} + +resource "aws_s3_bucket_object" "cache-index-html" { + provider = aws + + acl = "public-read" + bucket = aws_s3_bucket.cache.bucket + content_type = "text/html" + etag = filemd5("${path.module}/../cache-staging/index.html") + key = "index.html" + source = "${path.module}/../cache-staging/index.html" +} + +resource "aws_s3_bucket_policy" "cache" { + provider = aws + bucket = aws_s3_bucket.cache.id + + # imported from existing + policy = <S3 requests. See Fastly documentation: + # https://docs.fastly.com/en/guides/amazon-s3#using-an-amazon-s3-private-bucket + snippet { + name = "Authenticate S3 requests" + type = "miss" + priority = 100 + content = templatefile("${path.module}/cache-staging/s3-authn.vcl", { + aws_region = aws_s3_bucket.cache.region + backend_domain = aws_s3_bucket.cache.bucket_domain_name + access_key = local.cache-iam.key + secret_key = local.cache-iam.secret + }) + } + + snippet { + content = "set req.url = querystring.remove(req.url);" + name = "Remove all query strings" + priority = 50 + type = "recv" + } + + # Work around the 2GB size limit for large files + # + # See https://docs.fastly.com/en/guides/segmented-caching + snippet { + content = <<-EOT + if (req.url.path ~ "^/nar/") { + set req.enable_segmented_caching = true; + } + EOT + name = "Enable segment caching for NAR files" + priority = 60 + type = "recv" + } + + snippet { + name = "cache-errors" + content = <<-EOT + if (beresp.status == 403) { + set beresp.status = 404; + } + EOT + priority = 100 + type = "fetch" + } + + logging_s3 { + name = "${local.cache_staging_domain}-to-s3" + bucket_name = local.fastlylogs["bucket_name"] + compression_codec = "zstd" + domain = local.fastlylogs["s3_domain"] + format = local.fastlylogs["format"] + format_version = 2 + path = "${local.cache_staging_domain}/" + period = local.fastlylogs["period"] + message_type = "blank" + s3_iam_role = local.fastlylogs["iam_role_arn"] + } +} + +resource "fastly_tls_subscription" "cache-staging" { + domains = [for domain in fastly_service_vcl.cache-staging.domain : domain.name] + configuration_id = local.fastly_tls12_sni_configuration_id + certificate_authority = "globalsign" +} diff --git a/terraform/cache-staging/diagnostic.sh b/terraform/cache-staging/diagnostic.sh new file mode 100755 index 00000000..0b713942 --- /dev/null +++ b/terraform/cache-staging/diagnostic.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p bind.dnsutils -p mtr -p curl +# shellcheck shell=bash +# impure: needs ping +# +# Run this script if you are having issues with cache.nixos.org and paste the +# output URL in a new issue in the same repo. +# + +domain=${1:-cache-staging.nixos.org} + +run() { + echo "> $*" + "$@" |& sed -e "s/^/ /" + printf "Exit: %s\n\n\n" "$?" +} + +curl_w=" +time_namelookup: %{time_namelookup} +time_connect: %{time_connect} +time_appconnect: %{time_appconnect} +time_pretransfer: %{time_pretransfer} +time_redirect: %{time_redirect} +time_starttransfer: %{time_starttransfer} +time_total: %{time_total} +" + +curl_test() { + curl -w "$curl_w" -v -o /dev/null "$@" +} + +ix() { + url=$(cat | curl -F 'f:1=<-' ix.io 2>/dev/null) + echo "Pasted at: $url" +} + +( + echo "domain=$domain" + run dig -t A "$domain" + run ping -c1 "$domain" + run ping -4 -c1 "$domain" + run ping -6 -c1 "$domain" + run mtr -c 20 -w -r "$domain" + run curl_test -4 "http://$domain/" + run curl_test -6 "http://$domain/" + run curl_test -4 "https://$domain/" + run curl_test -6 "https://$domain/" + run curl -I -4 "https://$domain/" + run curl -I -4 "https://$domain/" + run curl -I -4 "https://$domain/" + run curl -I -6 "https://$domain/" + run curl -I -6 "https://$domain/" + run curl -I -6 "https://$domain/" +) | tee /dev/stderr | ix diff --git a/terraform/cache-staging/index.html b/terraform/cache-staging/index.html new file mode 100644 index 00000000..0cc5df27 --- /dev/null +++ b/terraform/cache-staging/index.html @@ -0,0 +1,60 @@ + + + + cache-staging.nixos.org is up + + + + + + + + +
+
+

+ + logo + +

+ +

+ https://cache.nixos.org/ provides + prebuilt binaries for Nixpkgs and NixOS. It is + used automatically by the Nix package manager to + speed up builds. +

+

+
+
+
+

+ If you are having trouble, please reach out through one of the + support channels + with the results of + this diagnostics script + which will help us figure out where the issue lies. +

+

+ For questions, or support, + the support page from the NixOS website describes how to get in touch. +

+
+
+ + diff --git a/terraform/cache-staging/nix-cache-info b/terraform/cache-staging/nix-cache-info new file mode 100644 index 00000000..7d239de8 --- /dev/null +++ b/terraform/cache-staging/nix-cache-info @@ -0,0 +1,3 @@ +StoreDir: /nix/store +WantMassQuery: 1 +Priority: 40 diff --git a/terraform/cache-staging/s3-authn.vcl b/terraform/cache-staging/s3-authn.vcl new file mode 100644 index 00000000..74d47006 --- /dev/null +++ b/terraform/cache-staging/s3-authn.vcl @@ -0,0 +1,65 @@ +# VCL snippet to authenticate Fastly<->S3 requests. +# +# https://docs.fastly.com/en/guides/amazon-s3#using-an-amazon-s3-private-bucket + +declare local var.canonicalHeaders STRING; +declare local var.signedHeaders STRING; +declare local var.canonicalRequest STRING; +declare local var.canonicalQuery STRING; +declare local var.stringToSign STRING; +declare local var.dateStamp STRING; +declare local var.signature STRING; +declare local var.scope STRING; + +if (req.method == "GET" && !req.backend.is_shield) { + set bereq.http.x-amz-content-sha256 = digest.hash_sha256(""); + set bereq.http.x-amz-date = strftime({"%Y%m%dT%H%M%SZ"}, now); + set bereq.http.x-amz-request-payer = "requester"; + set bereq.http.host = "${backend_domain}"; + set bereq.url = querystring.remove(bereq.url); + set bereq.url = regsuball(urlencode(urldecode(bereq.url.path)), {"%2F"}, "/"); + set var.dateStamp = strftime({"%Y%m%d"}, now); + set var.canonicalHeaders = "" + "host:" bereq.http.host LF + "x-amz-content-sha256:" bereq.http.x-amz-content-sha256 LF + "x-amz-date:" bereq.http.x-amz-date LF + "x-amz-request-payer:" bereq.http.x-amz-request-payer LF + ; + set var.canonicalQuery = ""; + set var.signedHeaders = "host;x-amz-content-sha256;x-amz-date;x-amz-request-payer"; + set var.canonicalRequest = "" + "GET" LF + bereq.url.path LF + var.canonicalQuery LF + var.canonicalHeaders LF + var.signedHeaders LF + digest.hash_sha256("") + ; + + set var.scope = var.dateStamp "/${aws_region}/s3/aws4_request"; + + set var.stringToSign = "" + "AWS4-HMAC-SHA256" LF + bereq.http.x-amz-date LF + var.scope LF + regsub(digest.hash_sha256(var.canonicalRequest),"^0x", "") + ; + + set var.signature = digest.awsv4_hmac( + "${secret_key}", + var.dateStamp, + "${aws_region}", + "s3", + var.stringToSign + ); + + set bereq.http.Authorization = "AWS4-HMAC-SHA256 " + "Credential=${access_key}/" var.scope ", " + "SignedHeaders=" var.signedHeaders ", " + "Signature=" + regsub(var.signature,"^0x", "") + ; + unset bereq.http.Accept; + unset bereq.http.Accept-Language; + unset bereq.http.User-Agent; + unset bereq.http.Fastly-Client-IP; +} diff --git a/terraform/dns.tf b/terraform/dns.tf index aa63051e..f47aefdb 100644 --- a/terraform/dns.tf +++ b/terraform/dns.tf @@ -131,6 +131,11 @@ locals { type = "CNAME" value = "dualstack.v2.shared.global.fastly.net" }, + { + hostname = "cache-staging.nixos.org" + type = "CNAME" + value = "dualstack.v2.shared.global.fastly.net" + }, { hostname = "channels.nixos.org" type = "CNAME"