@cryptotaxi247 / infra / commits / 73f99586

cache-staging: add fastly service configuration and test VCL for authentication (#295)

Pierre Bourdon committed Oct 27, 2023 at 19:18 UTC 73f99586cf2003cc13e1d2e0f25ba0f69ceb6b65
3 files changed +219
terraform/cache.tf
+153
@@ -254,6 +254,159 @@ resource "fastly_tls_subscription" "cache" {
254 certificate_authority = "globalsign"
255 }
256
257 +# Temporarily duplicated while testing VCL fragment for Fastly<->S3 authn. TF
258 +# doesn't make it particularly easy to avoid this duplication.
259 +#
260 +# TODO: remove once the Fastly<->S3 authn is deployed to the main cache
261 +# Fastly service.
262 +resource "fastly_service_vcl" "cache-staging" {
263 + name = "cache-staging.nixos.org"
264 + default_ttl = 86400
265 +
266 + backend {
267 + address = "s3.amazonaws.com"
268 + auto_loadbalance = false
269 + between_bytes_timeout = 10000
270 + connect_timeout = 5000
271 + error_threshold = 0
272 + first_byte_timeout = 15000
273 + max_conn = 200
274 + name = "s3.amazonaws.com"
275 + override_host = aws_s3_bucket.cache.bucket_domain_name
276 + port = 443
277 + shield = local.fastly_shield
278 + ssl_cert_hostname = "s3.amazonaws.com"
279 + ssl_check_cert = true
280 + use_ssl = true
281 + weight = 100
282 + }
283 +
284 + condition {
285 + name = "is-404"
286 + priority = 0
287 + statement = "beresp.status == 404"
288 + type = "CACHE"
289 + }
290 +
291 + condition {
292 + name = "Match /"
293 + priority = 10
294 + statement = "req.url ~ \"^/$\""
295 + type = "REQUEST"
296 + }
297 +
298 + domain {
299 + name = "cache-staging.nixos.org"
300 + }
301 +
302 + header {
303 + action = "set"
304 + destination = "url"
305 + ignore_if_set = false
306 + name = "Landing page"
307 + priority = 10
308 + request_condition = "Match /"
309 + source = "\"/index.html\""
310 + type = "request"
311 + }
312 +
313 + # Clean headers for caching
314 + header {
315 + destination = "http.x-amz-request-id"
316 + type = "cache"
317 + action = "delete"
318 + name = "remove x-amz-request-id"
319 + }
320 + header {
321 + destination = "http.x-amz-version-id"
322 + type = "cache"
323 + action = "delete"
324 + name = "remove x-amz-version-id"
325 + }
326 + header {
327 + destination = "http.x-amz-id-2"
328 + type = "cache"
329 + action = "delete"
330 + name = "remove x-amz-id-2"
331 + }
332 +
333 + # Enable Streaming Miss.
334 + # https://docs.fastly.com/en/guides/streaming-miss
335 + # https://github.com/NixOS/nixos-org-configurations/issues/212#issuecomment-1187568233
336 + header {
337 + priority = 20
338 + destination = "do_stream"
339 + type = "cache"
340 + action = "set"
341 + name = "Enabling Streaming Miss"
342 + source = "true"
343 + }
344 +
345 + # Allow CORS GET requests.
346 + header {
347 + destination = "http.access-control-allow-origin"
348 + type = "response"
349 + action = "set"
350 + name = "CORS Allow"
351 + source = "\"*\""
352 + }
353 +
354 + response_object {
355 + name = "404-page"
356 + cache_condition = "is-404"
357 + content = "404"
358 + content_type = "text/plain"
359 + response = "Not Found"
360 + status = 404
361 + }
362 +
363 + # Authenticate Fastly<->S3 requests. See Fastly documentation:
364 + # https://docs.fastly.com/en/guides/amazon-s3#using-an-amazon-s3-private-bucket
365 + snippet {
366 + name = "Authenticate S3 requests"
367 + type = "miss"
368 + priority = 100
369 + content = templatefile("${path.module}/cache/s3-authn.vcl", {
370 + aws_region = aws_s3_bucket.cache.region
371 + backend_domain = aws_s3_bucket.cache.bucket_domain_name
372 + access_key = local.cache-iam.key
373 + secret_key = local.cache-iam.secret
374 + })
375 + }
376 +
377 + snippet {
378 + content = "set req.url = querystring.remove(req.url);"
379 + name = "Remove all query strings"
380 + priority = 50
381 + type = "recv"
382 + }
383 +
384 + # Work around the 2GB size limit for large files
385 + #
386 + # See https://docs.fastly.com/en/guides/segmented-caching
387 + snippet {
388 + content = <<-EOT
389 + if (req.url.path ~ "^/nar/") {
390 + set req.enable_segmented_caching = true;
391 + }
392 + EOT
393 + name = "Enable segment caching for NAR files"
394 + priority = 60
395 + type = "recv"
396 + }
397 +
398 + snippet {
399 + name = "cache-errors"
400 + content = <<-EOT
401 + if (beresp.status == 403) {
402 + set beresp.status = 404;
403 + }
404 + EOT
405 + priority = 100
406 + type = "fetch"
407 + }
408 +}
409 +
410 resource "fastly_tls_subscription" "cache-staging" {
411 domains = ["cache-staging.nixos.org"]
412 configuration_id = local.fastly_tls12_sni_configuration_id
terraform/cache/s3-authn.vcl new
+65
@@ -0,0 +1,65 @@
1 +# VCL snippet to authenticate Fastly<->S3 requests.
2 +#
3 +# https://docs.fastly.com/en/guides/amazon-s3#using-an-amazon-s3-private-bucket
4 +
5 +declare local var.canonicalHeaders STRING;
6 +declare local var.signedHeaders STRING;
7 +declare local var.canonicalRequest STRING;
8 +declare local var.canonicalQuery STRING;
9 +declare local var.stringToSign STRING;
10 +declare local var.dateStamp STRING;
11 +declare local var.signature STRING;
12 +declare local var.scope STRING;
13 +
14 +if (req.method == "GET" && !req.backend.is_shield) {
15 + set bereq.http.x-amz-content-sha256 = digest.hash_sha256("");
16 + set bereq.http.x-amz-date = strftime({"%Y%m%dT%H%M%SZ"}, now);
17 + set bereq.http.x-amz-request-payer = "requester";
18 + set bereq.http.host = "${backend_domain}";
19 + set bereq.url = querystring.remove(bereq.url);
20 + set bereq.url = regsuball(urlencode(urldecode(bereq.url.path)), {"%2F"}, "/");
21 + set var.dateStamp = strftime({"%Y%m%d"}, now);
22 + set var.canonicalHeaders = ""
23 + "host:" bereq.http.host LF
24 + "x-amz-content-sha256:" bereq.http.x-amz-content-sha256 LF
25 + "x-amz-date:" bereq.http.x-amz-date LF
26 + "x-amz-request-payer:" bereq.http.x-amz-request-payer LF
27 + ;
28 + set var.canonicalQuery = "";
29 + set var.signedHeaders = "host;x-amz-content-sha256;x-amz-date;x-amz-request-payer";
30 + set var.canonicalRequest = ""
31 + "GET" LF
32 + bereq.url.path LF
33 + var.canonicalQuery LF
34 + var.canonicalHeaders LF
35 + var.signedHeaders LF
36 + digest.hash_sha256("")
37 + ;
38 +
39 + set var.scope = var.dateStamp "/${aws_region}/s3/aws4_request";
40 +
41 + set var.stringToSign = ""
42 + "AWS4-HMAC-SHA256" LF
43 + bereq.http.x-amz-date LF
44 + var.scope LF
45 + regsub(digest.hash_sha256(var.canonicalRequest),"^0x", "")
46 + ;
47 +
48 + set var.signature = digest.awsv4_hmac(
49 + "${secret_key}",
50 + var.dateStamp,
51 + "${aws_region}",
52 + "s3",
53 + var.stringToSign
54 + );
55 +
56 + set bereq.http.Authorization = "AWS4-HMAC-SHA256 "
57 + "Credential=${access_key}/" var.scope ", "
58 + "SignedHeaders=" var.signedHeaders ", "
59 + "Signature=" + regsub(var.signature,"^0x", "")
60 + ;
61 + unset bereq.http.Accept;
62 + unset bereq.http.Accept-Language;
63 + unset bereq.http.User-Agent;
64 + unset bereq.http.Fastly-Client-IP;
65 +}
terraform/locals.tf
+1
@@ -6,6 +6,7 @@ locals {
6
7 fastly_shield = "iad-va-us"
8
9 + cache-iam = data.terraform_remote_state.terraform-iam.outputs.cache
10 fastlylogs = data.terraform_remote_state.terraform-iam.outputs.fastlylogs
11
12 # fastlylogs = {