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 = {