| 1 | locals { |
| 2 | tarballs_domain = "tarballs.nixos.org" |
| 3 | # Use the website endpoint because the bucket is configured with website |
| 4 | # enabled. This also means we can't use TLS between Fastly and AWS because |
| 5 | # the website endpoint only has port 80 open. |
| 6 | tarballs_backend = "nixpkgs-tarballs.s3-website-eu-west-1.amazonaws.com" |
| 7 | # TODO: Uncomment this once has been applied once. This is to work around fastly bug https://github.com/fastly/terraform-provider-fastly/issues/884 |
| 8 | # tarballs_backend = aws_s3_bucket_website_configuration.nixpkgs-tarballs.website_endpoint |
| 9 | } |
| 10 | |
| 11 | resource "aws_s3_bucket" "nixpkgs-tarballs" { |
| 12 | bucket = "nixpkgs-tarballs" |
| 13 | } |
| 14 | |
| 15 | resource "aws_s3_bucket_website_configuration" "nixpkgs-tarballs" { |
| 16 | bucket = aws_s3_bucket.nixpkgs-tarballs.id |
| 17 | index_document { |
| 18 | suffix = "index.html" |
| 19 | } |
| 20 | } |
| 21 | |
| 22 | import { |
| 23 | to = aws_s3_bucket_website_configuration.nixpkgs-tarballs |
| 24 | id = aws_s3_bucket.nixpkgs-tarballs.id |
| 25 | } |
| 26 | |
| 27 | resource "aws_s3_bucket_policy" "nixpkgs-tarballs" { |
| 28 | bucket = aws_s3_bucket.nixpkgs-tarballs.id |
| 29 | |
| 30 | # imported from existing |
| 31 | policy = <<EOF |
| 32 | { |
| 33 | "Version": "2008-10-17", |
| 34 | "Statement": [ |
| 35 | { |
| 36 | "Sid": "AllowPublicRead", |
| 37 | "Effect": "Allow", |
| 38 | "Principal": { |
| 39 | "AWS": "*" |
| 40 | }, |
| 41 | "Action": "s3:GetObject", |
| 42 | "Resource": "arn:aws:s3:::nixpkgs-tarballs/*" |
| 43 | }, |
| 44 | { |
| 45 | "Sid": "AllowUpload", |
| 46 | "Effect": "Allow", |
| 47 | "Principal": { |
| 48 | "AWS": "arn:aws:iam::080433136561:user/s3-upload-tarballs" |
| 49 | }, |
| 50 | "Action": [ |
| 51 | "s3:PutObject", |
| 52 | "s3:PutObjectAcl" |
| 53 | ], |
| 54 | "Resource": "arn:aws:s3:::nixpkgs-tarballs/*" |
| 55 | }, |
| 56 | { |
| 57 | "Sid": "AllowUpload2", |
| 58 | "Effect": "Allow", |
| 59 | "Principal": { |
| 60 | "AWS": "arn:aws:iam::080433136561:user/s3-upload-tarballs" |
| 61 | }, |
| 62 | "Action": "s3:ListBucket", |
| 63 | "Resource": "arn:aws:s3:::nixpkgs-tarballs" |
| 64 | }, |
| 65 | { |
| 66 | "Sid": "CopumpkinAllowUpload", |
| 67 | "Effect": "Allow", |
| 68 | "Principal": { |
| 69 | "AWS": "arn:aws:iam::390897850978:root" |
| 70 | }, |
| 71 | "Action": [ |
| 72 | "s3:PutObject", |
| 73 | "s3:PutObjectAcl" |
| 74 | ], |
| 75 | "Resource": "arn:aws:s3:::nixpkgs-tarballs/*" |
| 76 | }, |
| 77 | { |
| 78 | "Sid": "CopumpkinAllowUpload2", |
| 79 | "Effect": "Allow", |
| 80 | "Principal": { |
| 81 | "AWS": "arn:aws:iam::390897850978:root" |
| 82 | }, |
| 83 | "Action": "s3:ListBucket", |
| 84 | "Resource": "arn:aws:s3:::nixpkgs-tarballs" |
| 85 | }, |
| 86 | { |
| 87 | "Sid": "ShlevyAllowUpload", |
| 88 | "Effect": "Allow", |
| 89 | "Principal": { |
| 90 | "AWS": "arn:aws:iam::976576280863:user/shlevy" |
| 91 | }, |
| 92 | "Action": [ |
| 93 | "s3:PutObject", |
| 94 | "s3:PutObjectAcl" |
| 95 | ], |
| 96 | "Resource": "arn:aws:s3:::nixpkgs-tarballs/*" |
| 97 | }, |
| 98 | { |
| 99 | "Sid": "ShlevyAllowUpload2", |
| 100 | "Effect": "Allow", |
| 101 | "Principal": { |
| 102 | "AWS": "arn:aws:iam::976576280863:user/shlevy" |
| 103 | }, |
| 104 | "Action": "s3:ListBucket", |
| 105 | "Resource": "arn:aws:s3:::nixpkgs-tarballs" |
| 106 | }, |
| 107 | { |
| 108 | "Sid": "DaiderdAllowUpload", |
| 109 | "Effect": "Allow", |
| 110 | "Principal": { |
| 111 | "AWS": "arn:aws:iam::014292808257:user/lnl7" |
| 112 | }, |
| 113 | "Action": [ |
| 114 | "s3:PutObject", |
| 115 | "s3:PutObjectAcl" |
| 116 | ], |
| 117 | "Resource": "arn:aws:s3:::nixpkgs-tarballs/*" |
| 118 | }, |
| 119 | { |
| 120 | "Sid": "DaiderdAllowUpload2", |
| 121 | "Effect": "Allow", |
| 122 | "Principal": { |
| 123 | "AWS": "arn:aws:iam::014292808257:user/lnl7" |
| 124 | }, |
| 125 | "Action": "s3:ListBucket", |
| 126 | "Resource": "arn:aws:s3:::nixpkgs-tarballs" |
| 127 | }, |
| 128 | { |
| 129 | "Sid": "LovesegfaultAllowUpload", |
| 130 | "Effect": "Allow", |
| 131 | "Principal": { |
| 132 | "AWS": "arn:aws:iam::839273551904:root" |
| 133 | }, |
| 134 | "Action": [ |
| 135 | "s3:PutObject", |
| 136 | "s3:PutObjectAcl" |
| 137 | ], |
| 138 | "Resource": "arn:aws:s3:::nixpkgs-tarballs/*" |
| 139 | }, |
| 140 | { |
| 141 | "Sid": "LovesegfaultAllowUpload2", |
| 142 | "Effect": "Allow", |
| 143 | "Principal": { |
| 144 | "AWS": "arn:aws:iam::839273551904:root" |
| 145 | }, |
| 146 | "Action": "s3:ListBucket", |
| 147 | "Resource": "arn:aws:s3:::nixpkgs-tarballs" |
| 148 | } |
| 149 | ] |
| 150 | } |
| 151 | EOF |
| 152 | } |
| 153 | |
| 154 | resource "aws_s3_bucket_object" "nixpkgs-tarballs-index" { |
| 155 | bucket = aws_s3_bucket.nixpkgs-tarballs.id |
| 156 | content_type = "text/html" |
| 157 | etag = filemd5("${path.module}/nixpkgs-tarballs/index.html") |
| 158 | key = "index.html" |
| 159 | source = "${path.module}/nixpkgs-tarballs/index.html" |
| 160 | } |
| 161 | |
| 162 | resource "fastly_service_vcl" "nixpkgs-tarballs" { |
| 163 | name = local.tarballs_domain |
| 164 | default_ttl = 86400 |
| 165 | |
| 166 | backend { |
| 167 | address = local.tarballs_backend |
| 168 | auto_loadbalance = false |
| 169 | between_bytes_timeout = 10000 |
| 170 | connect_timeout = 5000 |
| 171 | error_threshold = 0 |
| 172 | first_byte_timeout = 15000 |
| 173 | max_conn = 200 |
| 174 | name = local.tarballs_backend |
| 175 | override_host = local.tarballs_backend |
| 176 | port = 80 |
| 177 | shield = "dub-dublin-ie" |
| 178 | use_ssl = false |
| 179 | weight = 100 |
| 180 | } |
| 181 | |
| 182 | request_setting { |
| 183 | name = "Redirect HTTP to HTTPS" |
| 184 | force_ssl = true |
| 185 | } |
| 186 | |
| 187 | condition { |
| 188 | name = "Generated by synthetic response for 404 page" |
| 189 | priority = 0 |
| 190 | statement = "beresp.status == 404" |
| 191 | type = "CACHE" |
| 192 | } |
| 193 | |
| 194 | condition { |
| 195 | name = "Match /" |
| 196 | priority = 10 |
| 197 | statement = "req.url ~ \"^/$\"" |
| 198 | type = "REQUEST" |
| 199 | } |
| 200 | |
| 201 | domain { |
| 202 | name = local.tarballs_domain |
| 203 | } |
| 204 | |
| 205 | header { |
| 206 | action = "set" |
| 207 | destination = "url" |
| 208 | ignore_if_set = false |
| 209 | name = "Landing page" |
| 210 | priority = 10 |
| 211 | request_condition = "Match /" |
| 212 | source = "\"/index.html\"" |
| 213 | type = "request" |
| 214 | } |
| 215 | |
| 216 | # Clean headers for caching |
| 217 | header { |
| 218 | destination = "http.x-amz-request-id" |
| 219 | type = "cache" |
| 220 | action = "delete" |
| 221 | name = "remove x-amz-request-id" |
| 222 | } |
| 223 | header { |
| 224 | destination = "http.x-amz-version-id" |
| 225 | type = "cache" |
| 226 | action = "delete" |
| 227 | name = "remove x-amz-version-id" |
| 228 | } |
| 229 | header { |
| 230 | destination = "http.x-amz-id-2" |
| 231 | type = "cache" |
| 232 | action = "delete" |
| 233 | name = "remove x-amz-id-2" |
| 234 | } |
| 235 | |
| 236 | # Allow CORS GET requests. |
| 237 | header { |
| 238 | destination = "http.access-control-allow-origin" |
| 239 | type = "cache" |
| 240 | action = "set" |
| 241 | name = "CORS Allow" |
| 242 | source = "\"*\"" |
| 243 | } |
| 244 | |
| 245 | response_object { |
| 246 | cache_condition = "Generated by synthetic response for 404 page" |
| 247 | content = "404" |
| 248 | content_type = "text/html" |
| 249 | name = "Generated by synthetic response for 404 page" |
| 250 | response = "Not Found" |
| 251 | status = 404 |
| 252 | } |
| 253 | |
| 254 | snippet { |
| 255 | content = "set req.url = querystring.remove(req.url);" |
| 256 | name = "Remove all query strings" |
| 257 | priority = 50 |
| 258 | type = "recv" |
| 259 | } |
| 260 | |
| 261 | snippet { |
| 262 | content = <<-EOT |
| 263 | if (beresp.status == 403) { |
| 264 | set beresp.status = 404; |
| 265 | set beresp.ttl = 86400s; |
| 266 | set beresp.grace = 0s; |
| 267 | set beresp.cacheable = true; |
| 268 | } |
| 269 | EOT |
| 270 | name = "Change 403 from S3 to 404" |
| 271 | priority = 100 |
| 272 | type = "fetch" |
| 273 | } |
| 274 | |
| 275 | logging_s3 { |
| 276 | name = "${local.tarballs_domain}-to-s3" |
| 277 | bucket_name = local.fastlylogs["bucket_name"] |
| 278 | compression_codec = "zstd" |
| 279 | domain = local.fastlylogs["s3_domain"] |
| 280 | format = local.fastlylogs["format"] |
| 281 | format_version = 2 |
| 282 | path = "${local.tarballs_domain}/" |
| 283 | period = local.fastlylogs["period"] |
| 284 | message_type = "blank" |
| 285 | s3_iam_role = local.fastlylogs["iam_role_arn"] |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | resource "fastly_tls_subscription" "nixpkgs-tarballs-2025-11" { |
| 290 | domains = [for domain in fastly_service_vcl.nixpkgs-tarballs.domain : domain.name] |
| 291 | configuration_id = local.fastly_tls13_quic_configuration_id |
| 292 | certificate_authority = "lets-encrypt" |
| 293 | } |
| 294 | |
| 295 | output "nixpkgs-tarballs-managed_dns_challenge" { |
| 296 | value = fastly_tls_subscription.nixpkgs-tarballs-2025-11.managed_dns_challenges |
| 297 | } |
| 298 | |
| 299 | # Create an S3 bucket for CloudTrail logs |
| 300 | resource "aws_s3_bucket" "nixpkgs-tarballs-cloudtrail-logs" { |
| 301 | bucket = "nixpkgs-tarballs-cloudtrail-logs" |
| 302 | # We can potentially make this public for transparency? |
| 303 | # But first I want to see what the logs look like. |
| 304 | acl = "private" |
| 305 | } |
| 306 | |
| 307 | resource "aws_s3_bucket_versioning" "nixpkgs-tarballs-cloudtrail-logs" { |
| 308 | bucket = aws_s3_bucket.nixpkgs-tarballs-cloudtrail-logs.id |
| 309 | versioning_configuration { |
| 310 | status = "Enabled" |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | |
| 315 | import { |
| 316 | to = aws_s3_bucket_versioning.nixpkgs-tarballs-cloudtrail-logs |
| 317 | id = aws_s3_bucket.nixpkgs-tarballs-cloudtrail-logs.id |
| 318 | } |
| 319 | |
| 320 | # Attach a policy to the CloudTrail logs S3 bucket |
| 321 | data "aws_iam_policy_document" "nixpkgs-tarballs-cloudtrail-logs-policy" { |
| 322 | statement { |
| 323 | sid = "AWSCloudTrailAclCheck" |
| 324 | effect = "Allow" |
| 325 | |
| 326 | principals { |
| 327 | type = "Service" |
| 328 | identifiers = ["cloudtrail.amazonaws.com"] |
| 329 | } |
| 330 | |
| 331 | actions = ["s3:GetBucketAcl"] |
| 332 | resources = [aws_s3_bucket.nixpkgs-tarballs-cloudtrail-logs.arn] |
| 333 | condition { |
| 334 | test = "StringEquals" |
| 335 | variable = "aws:SourceArn" |
| 336 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/nixpkgs-tarballs"] |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | statement { |
| 341 | sid = "AWSCloudTrailWrite" |
| 342 | effect = "Allow" |
| 343 | |
| 344 | principals { |
| 345 | type = "Service" |
| 346 | identifiers = ["cloudtrail.amazonaws.com"] |
| 347 | } |
| 348 | |
| 349 | actions = ["s3:PutObject"] |
| 350 | resources = ["${aws_s3_bucket.nixpkgs-tarballs-cloudtrail-logs.arn}/*"] |
| 351 | |
| 352 | condition { |
| 353 | test = "StringEquals" |
| 354 | variable = "s3:x-amz-acl" |
| 355 | values = ["bucket-owner-full-control"] |
| 356 | } |
| 357 | condition { |
| 358 | test = "StringEquals" |
| 359 | variable = "aws:SourceArn" |
| 360 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/nixpkgs-tarballs"] |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | data "aws_caller_identity" "current" {} |
| 366 | data "aws_partition" "current" {} |
| 367 | data "aws_region" "current" {} |
| 368 | |
| 369 | resource "aws_s3_bucket_policy" "nixpkgs-tarballs-cloudtrail-logs-policy" { |
| 370 | bucket = aws_s3_bucket.nixpkgs-tarballs-cloudtrail-logs.id |
| 371 | policy = data.aws_iam_policy_document.nixpkgs-tarballs-cloudtrail-logs-policy.json |
| 372 | } |
| 373 | |
| 374 | # Create a CloudTrail |
| 375 | resource "aws_cloudtrail" "nixpkgs-tarballs" { |
| 376 | name = "nixpkgs-tarballs" |
| 377 | s3_bucket_name = aws_s3_bucket.nixpkgs-tarballs-cloudtrail-logs.bucket |
| 378 | enable_log_file_validation = true |
| 379 | depends_on = [ |
| 380 | aws_s3_bucket_policy.nixpkgs-tarballs-cloudtrail-logs-policy |
| 381 | ] |
| 382 | # You must specify a log group and a role ARN. |
| 383 | |
| 384 | event_selector { |
| 385 | read_write_type = "WriteOnly" |
| 386 | include_management_events = false |
| 387 | |
| 388 | data_resource { |
| 389 | type = "AWS::S3::Object" |
| 390 | values = ["arn:aws:s3:::${aws_s3_bucket.nixpkgs-tarballs.bucket}/"] |
| 391 | } |
| 392 | } |
| 393 | } |