main
tf 343 lines 8.23 KB
Raw
1 locals {
2 cache_domain = "cache.nixos.org"
3 }
4
5 resource "aws_s3_bucket" "cache" {
6 provider = aws.us
7 bucket = "nix-cache"
8 }
9
10 resource "aws_s3_bucket_versioning" "cache" {
11 provider = aws.us
12 bucket = aws_s3_bucket.cache.id
13 versioning_configuration {
14 status = "Enabled"
15 }
16 }
17
18 resource "aws_s3_bucket_lifecycle_configuration" "cache" {
19 provider = aws.us
20 bucket = aws_s3_bucket.cache.id
21
22 depends_on = [aws_s3_bucket_versioning.cache]
23
24 transition_default_minimum_object_size = "varies_by_storage_class"
25
26 rule {
27 id = "Infrequent Access"
28 status = "Enabled"
29
30 filter {
31 prefix = ""
32 }
33
34 transition {
35 days = 0
36 storage_class = "INTELLIGENT_TIERING"
37 }
38 }
39
40 # We delete no-current versions after 30 days
41 rule {
42 id = "Non-current Versions"
43 status = "Enabled"
44
45 noncurrent_version_expiration {
46 noncurrent_days = 30
47 }
48 }
49
50 # Reap multipart uploads (large NARs) that were never completed, e.g. when a
51 # build aborts mid-upload. Uploads normally finish within an hour.
52 rule {
53 id = "Abort incomplete multipart uploads"
54 status = "Enabled"
55
56 filter {}
57
58 abort_incomplete_multipart_upload {
59 days_after_initiation = 7
60 }
61 }
62 }
63
64 import {
65 to = aws_s3_bucket_lifecycle_configuration.cache
66 id = aws_s3_bucket.cache.id
67 }
68
69 resource "aws_s3_bucket_cors_configuration" "cache" {
70 provider = aws.us
71 bucket = aws_s3_bucket.cache.id
72 cors_rule {
73 allowed_headers = ["Authorization"]
74 allowed_methods = ["GET"]
75 allowed_origins = ["*"]
76 max_age_seconds = 3000
77 }
78 }
79
80 import {
81 to = aws_s3_bucket_cors_configuration.cache
82 id = aws_s3_bucket.cache.id
83 }
84
85 resource "aws_s3_bucket_object" "cache-nix-cache-info" {
86 provider = aws.us
87
88 acl = "public-read"
89 bucket = aws_s3_bucket.cache.bucket
90 content_type = "text/x-nix-cache-info"
91 etag = filemd5("${path.module}/cache/nix-cache-info")
92 key = "nix-cache-info"
93 source = "${path.module}/cache/nix-cache-info"
94 }
95
96 resource "aws_s3_bucket_object" "cache-index-html" {
97 provider = aws.us
98
99 acl = "public-read"
100 bucket = aws_s3_bucket.cache.bucket
101 content_type = "text/html"
102 etag = filemd5("${path.module}/cache/index.html")
103 key = "index.html"
104 source = "${path.module}/cache/index.html"
105 }
106
107 resource "aws_s3_bucket_policy" "cache" {
108 provider = aws.us
109 bucket = aws_s3_bucket.cache.id
110
111 # imported from existing
112 policy = <<EOF
113 {
114 "Version": "2008-10-17",
115 "Statement": [
116 {
117 "Sid": "AllowPublicRead",
118 "Effect": "Allow",
119 "Principal": {
120 "AWS": "*"
121 },
122 "Action": "s3:GetObject",
123 "Resource": "arn:aws:s3:::nix-cache/*"
124 },
125 {
126 "Sid": "AllowUploadDebuginfoWrite",
127 "Effect": "Allow",
128 "Principal": {
129 "AWS": "arn:aws:iam::080433136561:user/s3-upload-releases"
130 },
131 "Action": [
132 "s3:PutObject",
133 "s3:PutObjectAcl"
134 ],
135 "Resource": "arn:aws:s3:::nix-cache/debuginfo/*"
136 },
137 {
138 "Sid": "AllowUploadDebuginfoRead",
139 "Effect": "Allow",
140 "Principal": {
141 "AWS": "arn:aws:iam::080433136561:user/s3-upload-releases"
142 },
143 "Action": "s3:GetObject",
144 "Resource": "arn:aws:s3:::nix-cache/*"
145 },
146 {
147 "Sid": "AllowUploadDebuginfoRead2",
148 "Effect": "Allow",
149 "Principal": {
150 "AWS": "arn:aws:iam::080433136561:user/s3-upload-releases"
151 },
152 "Action": [
153 "s3:ListBucket",
154 "s3:GetBucketLocation"
155 ],
156 "Resource": "arn:aws:s3:::nix-cache"
157 }
158 ]
159 }
160 EOF
161 }
162
163 resource "aws_s3_bucket_request_payment_configuration" "cache" {
164 provider = aws.us
165 bucket = aws_s3_bucket.cache.id
166 payer = "Requester"
167 }
168
169 resource "fastly_service_vcl" "cache" {
170 name = local.cache_domain
171 default_ttl = 86400
172
173 backend {
174 address = "s3.amazonaws.com"
175 auto_loadbalance = false
176 between_bytes_timeout = 10000
177 connect_timeout = 5000
178 error_threshold = 0
179 first_byte_timeout = 15000
180 max_conn = 200
181 name = "s3.amazonaws.com"
182 override_host = aws_s3_bucket.cache.bucket_domain_name
183 port = 443
184 shield = "iad-va-us"
185 ssl_cert_hostname = "s3.amazonaws.com"
186 ssl_check_cert = true
187 use_ssl = true
188 weight = 100
189 }
190
191 request_setting {
192 name = "Redirect HTTP to HTTPS"
193 force_ssl = true
194 }
195
196 condition {
197 name = "is-404"
198 priority = 0
199 statement = "beresp.status == 404"
200 type = "CACHE"
201 }
202
203 condition {
204 name = "Match /"
205 priority = 10
206 statement = "req.url ~ \"^/$\""
207 type = "REQUEST"
208 }
209
210 domain {
211 name = "cache.nixos.org"
212 }
213
214 header {
215 action = "set"
216 destination = "url"
217 ignore_if_set = false
218 name = "Landing page"
219 priority = 10
220 request_condition = "Match /"
221 source = "\"/index.html\""
222 type = "request"
223 }
224
225 # Clean headers for caching
226 header {
227 destination = "http.x-amz-request-id"
228 type = "cache"
229 action = "delete"
230 name = "remove x-amz-request-id"
231 }
232 header {
233 destination = "http.x-amz-version-id"
234 type = "cache"
235 action = "delete"
236 name = "remove x-amz-version-id"
237 }
238 header {
239 destination = "http.x-amz-id-2"
240 type = "cache"
241 action = "delete"
242 name = "remove x-amz-id-2"
243 }
244
245 # Enable Streaming Miss.
246 # https://docs.fastly.com/en/guides/streaming-miss
247 # https://github.com/NixOS/infra/issues/212#issuecomment-1187568233
248 header {
249 priority = 20
250 destination = "do_stream"
251 type = "cache"
252 action = "set"
253 name = "Enabling Streaming Miss"
254 source = "true"
255 }
256
257 # Allow CORS GET requests.
258 header {
259 destination = "http.access-control-allow-origin"
260 type = "response"
261 action = "set"
262 name = "CORS Allow"
263 source = "\"*\""
264 }
265
266 response_object {
267 name = "404-page"
268 cache_condition = "is-404"
269 content = "404"
270 content_type = "text/plain"
271 response = "Not Found"
272 status = 404
273 }
274
275 # Authenticate Fastly<->S3 requests. See Fastly documentation:
276 # https://docs.fastly.com/en/guides/amazon-s3#using-an-amazon-s3-private-bucket
277 snippet {
278 name = "Authenticate S3 requests"
279 type = "miss"
280 priority = 100
281 content = templatefile("${path.module}/cache/s3-authn.vcl", {
282 aws_region = aws_s3_bucket.cache.region
283 backend_domain = aws_s3_bucket.cache.bucket_domain_name
284 access_key = local.cache-iam.key
285 secret_key = local.cache-iam.secret
286 })
287 }
288
289 snippet {
290 content = "set req.url = querystring.remove(req.url);"
291 name = "Remove all query strings"
292 priority = 50
293 type = "recv"
294 }
295
296 # Work around the 2GB size limit for large files
297 #
298 # See https://docs.fastly.com/en/guides/segmented-caching
299 snippet {
300 content = <<-EOT
301 if (req.url.path ~ "^/nar/") {
302 set req.enable_segmented_caching = true;
303 }
304 EOT
305 name = "Enable segment caching for NAR files"
306 priority = 60
307 type = "recv"
308 }
309
310 snippet {
311 name = "cache-errors"
312 content = <<-EOT
313 if (beresp.status == 403) {
314 set beresp.status = 404;
315 }
316 EOT
317 priority = 100
318 type = "fetch"
319 }
320
321 logging_s3 {
322 name = "${local.cache_domain}-to-s3"
323 bucket_name = local.fastlylogs["bucket_name"]
324 compression_codec = "zstd"
325 domain = local.fastlylogs["s3_domain"]
326 format = local.fastlylogs["format"]
327 format_version = 2
328 path = "${local.cache_domain}/"
329 period = local.fastlylogs["period"]
330 message_type = "blank"
331 s3_iam_role = local.fastlylogs["iam_role_arn"]
332 }
333 }
334
335 resource "fastly_tls_subscription" "cache-2025-11" {
336 domains = [for domain in fastly_service_vcl.cache.domain : domain.name]
337 configuration_id = local.fastly_tls13_quic_configuration_id
338 certificate_authority = "lets-encrypt"
339 }
340
341 output "cache-managed_dns_challenge" {
342 value = fastly_tls_subscription.cache-2025-11.managed_dns_challenges
343 }