summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Carstairs <65492573+joeacarstairs@users.noreply.github.com>2024-05-05 21:00:40 +0100
committerGitHub <noreply@github.com>2024-05-05 21:00:40 +0100
commit86450b3dd8bec3b76e2b2a03b8cdc8a708c5f3bc (patch)
tree7576d8863193369adc87dd80d91a3d5d3f58ede1
parent579e12cfebce9a70ab2adf5842e2673673a707d2 (diff)
Infrastructure as code (#3)
* Moves website to website/ * Adds terraform gitignores * Terraform with AWS provider * Initialises Terraform * Locals and variables for provider * Fetches SSL certificate from ACM * S3 static website bucket * CloudFront distribution * Route53 records * Deployment workflow uses secret S3 bucket suffix * Adds README --------- Co-authored-by: Joe Carstairs <65492573+Sycamost@users.noreply.github.com>
-rw-r--r--.github/workflows/build-and-deploy.yml5
-rw-r--r--.gitignore6
-rw-r--r--README.md28
-rw-r--r--infrastructure/.terraform.lock.hcl25
-rw-r--r--infrastructure/acm.tf4
-rw-r--r--infrastructure/cloudfront.tf59
-rw-r--r--infrastructure/locals.tf5
-rw-r--r--infrastructure/main.tf11
-rw-r--r--infrastructure/providers.tf12
-rw-r--r--infrastructure/route53.tf35
-rw-r--r--infrastructure/s3.tf78
-rw-r--r--infrastructure/variables.tf28
-rw-r--r--website/astro.config.mjs (renamed from astro.config.mjs)0
-rw-r--r--website/package-lock.json (renamed from package-lock.json)0
-rw-r--r--website/package.json (renamed from package.json)0
-rw-r--r--website/public/css/base.css (renamed from public/css/base.css)0
-rw-r--r--website/public/css/blog.css (renamed from public/css/blog.css)0
-rw-r--r--website/public/css/hcard.css (renamed from public/css/hcard.css)0
-rw-r--r--website/public/css/reset.css (renamed from public/css/reset.css)0
-rw-r--r--website/public/images/headshot.jpg (renamed from public/images/headshot.jpg)bin971952 -> 971952 bytes
-rw-r--r--website/public/images/headshot_large.jpg (renamed from public/images/headshot_large.jpg)bin971952 -> 971952 bytes
-rw-r--r--website/src/components/BaseHead.astro (renamed from src/components/BaseHead.astro)0
-rw-r--r--website/src/components/BlogFeed.astro (renamed from src/components/BlogFeed.astro)0
-rw-r--r--website/src/components/FormattedDate.astro (renamed from src/components/FormattedDate.astro)0
-rw-r--r--website/src/components/Me.astro (renamed from src/components/Me.astro)0
-rw-r--r--website/src/consts.ts (renamed from src/consts.ts)0
-rw-r--r--website/src/content/blog/2024/01/14/sapiens_on_religion.md (renamed from src/content/blog/2024/01/14/sapiens_on_religion.md)0
-rw-r--r--website/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md (renamed from src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md)0
-rw-r--r--website/src/content/blog/2024/03/30/easter.md (renamed from src/content/blog/2024/03/30/easter.md)0
-rw-r--r--website/src/content/config.ts (renamed from src/content/config.ts)0
-rw-r--r--website/src/env.d.ts (renamed from src/env.d.ts)0
-rw-r--r--website/src/layouts/BlogPost.astro (renamed from src/layouts/BlogPost.astro)0
-rw-r--r--website/src/layouts/Page.astro (renamed from src/layouts/Page.astro)0
-rw-r--r--website/src/pages/blog/[...slug].astro (renamed from src/pages/blog/[...slug].astro)0
-rw-r--r--website/src/pages/blog/index.astro (renamed from src/pages/blog/index.astro)0
-rw-r--r--website/src/pages/error.astro (renamed from src/pages/error.astro)0
-rw-r--r--website/src/pages/index.astro (renamed from src/pages/index.astro)0
-rw-r--r--website/src/pages/rss.xml.js (renamed from src/pages/rss.xml.js)0
-rw-r--r--website/src/src/content/blog/2024/04/10/tracking_pixels.md (renamed from src/content/blog/2024/04/10/tracking_pixels.md)0
-rw-r--r--website/tsconfig.json (renamed from tsconfig.json)0
40 files changed, 293 insertions, 3 deletions
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index 0ea882e..8b5add3 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -1,8 +1,6 @@
name: Build and deploy
on:
- pull_request:
- branches: ["main"]
push:
branches: ["main"]
workflow_dispatch:
@@ -20,6 +18,7 @@ jobs:
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ S3_BUCKET_SUFFIX: ${{ secrets.S3_BUCKET_SUFFIX }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -43,4 +42,4 @@ jobs:
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
aws configure set region eu-west-2
- aws s3 sync ./dist/ s3://joeac-personal-website --delete
+ aws s3 sync ./dist/ "s3://joeac.net-$S3_BUCKET_SUFFIX" --delete
diff --git a/.gitignore b/.gitignore
index 6240da8..5d688fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,9 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
+
+.terraform/
+**/*.tfvars
+**/*.tfstate
+**/*.tfstate.backup
+
diff --git a/README.md b/README.md
index 28178f6..13f249d 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,34 @@
Joe Carstairs' personal website
+Structure:
+
+├website: My public-facing website
+└infrastructure: The infrastructure of my website as code
+
+## Infrastructure
+
+The infrastructure has these components:
+
+- AWS Route53Domains (for domain name registration)
+- AWS Route53 (for domain name resolution)
+- AWS CloudFront (for path-based routing)
+- AWS S3 (for static website hosting)
+
+The CloudFront bit is needed, because S3 static website hosting can only accept
+HTTP requests. CloudFront manages receiving HTTPS requests and forwarding them
+to HTTP.
+
+The S3 bucket includes a secret string of random characters. This is because
+when you set up static website hosting, the S3 API becomes open to the internet,
+and there's no way to turn this off. So you are theoretically open to DDoS
+attacks, for which you will be charged. Including a random string in the bucket
+name makes it less likely that an attacker will find the bucket to send requests
+to.
+
+The secret is stored in a GitHub secret called `S3_BUCKET_SUFFIX` so that it can
+be accessed by GitHub Actions workflows.
+
## Invalidating the CloudFront cache
When you update pages, you’ll need to invalidate the CloudFront cache in order
diff --git a/infrastructure/.terraform.lock.hcl b/infrastructure/.terraform.lock.hcl
new file mode 100644
index 0000000..8790b90
--- /dev/null
+++ b/infrastructure/.terraform.lock.hcl
@@ -0,0 +1,25 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+ version = "4.67.0"
+ constraints = "~> 4.16"
+ hashes = [
+ "h1:LfOuBkdYCzQhtiRvVIxdP/KGJODa3cRsKjn8xKCTbVY=",
+ "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060",
+ "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6",
+ "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183",
+ "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1",
+ "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29",
+ "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7",
+ "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043",
+ "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362",
+ "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b",
+ "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+ "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf",
+ "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b",
+ "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c",
+ "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c",
+ "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d",
+ ]
+}
diff --git a/infrastructure/acm.tf b/infrastructure/acm.tf
new file mode 100644
index 0000000..f07a0eb
--- /dev/null
+++ b/infrastructure/acm.tf
@@ -0,0 +1,4 @@
+data "aws_acm_certificate" "joeac_ssl_certificate" {
+ domain = local.domain
+ statuses = ["ISSUED"]
+}
diff --git a/infrastructure/cloudfront.tf b/infrastructure/cloudfront.tf
new file mode 100644
index 0000000..4cdc80c
--- /dev/null
+++ b/infrastructure/cloudfront.tf
@@ -0,0 +1,59 @@
+resource "aws_cloudfront_distribution" "joeac" {
+ enabled = true
+ is_ipv6_enabled = true
+ default_root_object = "index.html"
+ price_class = "PriceClass_100"
+
+ aliases = ["joeac.net"]
+
+ origin {
+ domain_name = aws_s3_bucket_website_configuration.website.website_endpoint
+ origin_id = local.website_origin_id
+
+ custom_origin_config {
+ http_port = 80
+ https_port = 443
+ origin_protocol_policy = "http-only"
+ origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
+ }
+ }
+
+ default_cache_behavior {
+ allowed_methods = ["GET", "HEAD", "OPTIONS"]
+ cached_methods = ["GET", "HEAD", "OPTIONS"]
+ target_origin_id = local.website_origin_id
+ cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimized.id
+ viewer_protocol_policy = "redirect-to-https"
+ }
+
+ restrictions {
+ geo_restriction {
+ restriction_type = "none"
+ locations = []
+ }
+ }
+
+ viewer_certificate {
+ acm_certificate_arn = data.aws_acm_certificate.joeac_ssl_certificate.arn
+ ssl_support_method = "sni-only"
+ minimum_protocol_version = "TLSv1.2_2021"
+ }
+
+ depends_on = [aws_cloudfront_origin_access_control.website]
+}
+
+resource "aws_cloudfront_origin_access_control" "website" {
+ name = "website"
+ origin_access_control_origin_type = "s3"
+ signing_behavior = "always"
+ signing_protocol = "sigv4"
+}
+
+data "aws_cloudfront_cache_policy" "caching_optimized" {
+ name = "Managed-CachingOptimized"
+}
+
+locals {
+ website_origin_id = "website"
+}
+
diff --git a/infrastructure/locals.tf b/infrastructure/locals.tf
new file mode 100644
index 0000000..d4fd3bf
--- /dev/null
+++ b/infrastructure/locals.tf
@@ -0,0 +1,5 @@
+locals {
+ aws_region = "us-east-1"
+ domain = "joeac.net"
+}
+
diff --git a/infrastructure/main.tf b/infrastructure/main.tf
new file mode 100644
index 0000000..03b8568
--- /dev/null
+++ b/infrastructure/main.tf
@@ -0,0 +1,11 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 4.16"
+ }
+ }
+
+ required_version = ">= 1.2.0"
+}
+
diff --git a/infrastructure/providers.tf b/infrastructure/providers.tf
new file mode 100644
index 0000000..8e06505
--- /dev/null
+++ b/infrastructure/providers.tf
@@ -0,0 +1,12 @@
+provider "aws" {
+ region = local.aws_region
+ access_key = var.aws_access_key
+ secret_key = var.aws_secret_key
+ default_tags {
+ tags = {
+ project = "joeac-website"
+ owner = "terraform"
+ }
+ }
+}
+
diff --git a/infrastructure/route53.tf b/infrastructure/route53.tf
new file mode 100644
index 0000000..828cec2
--- /dev/null
+++ b/infrastructure/route53.tf
@@ -0,0 +1,35 @@
+# This hosted zone must have NS records which point to the same nameservers as
+# those listed with the domain registrar. Right now, this means manually going
+# into Route53Domains, finding the nameservers, and manually copying these into
+# the NS records for this hosted zone.
+resource "aws_route53_zone" "joeac_zone" {
+ name = "joeac.net"
+}
+
+resource "aws_route53_record" "cloudfront" {
+ # This is the subdomain for the record. Specify the root domain by leaving it blank
+ name = ""
+
+ zone_id = aws_route53_zone.joeac_zone.id
+ type = "A"
+
+ alias {
+ name = aws_cloudfront_distribution.joeac.domain_name
+ zone_id = aws_cloudfront_distribution.joeac.hosted_zone_id
+ evaluate_target_health = false
+ }
+}
+
+resource "aws_route53_record" "cloudfront_aaaa" {
+ # This is the subdomain for the record. Specify the root domain by leaving it blank
+ name = ""
+
+ zone_id = aws_route53_zone.joeac_zone.id
+ type = "AAAA"
+
+ alias {
+ name = aws_cloudfront_distribution.joeac.domain_name
+ zone_id = aws_cloudfront_distribution.joeac.hosted_zone_id
+ evaluate_target_health = false
+ }
+}
diff --git a/infrastructure/s3.tf b/infrastructure/s3.tf
new file mode 100644
index 0000000..7b116a7
--- /dev/null
+++ b/infrastructure/s3.tf
@@ -0,0 +1,78 @@
+resource "aws_s3_bucket" "website" {
+ bucket = local.bucket_name
+}
+
+locals {
+ bucket_name = "${local.domain}-${var.secret_s3_bucket_suffix}"
+}
+
+resource "aws_s3_bucket_website_configuration" "website" {
+ bucket = aws_s3_bucket.website.id
+
+ index_document {
+ suffix = "index.html"
+ }
+
+ error_document {
+ key = "error/index.html"
+ }
+}
+
+resource "aws_s3_bucket_ownership_controls" "website" {
+ bucket = aws_s3_bucket.website.id
+
+ rule {
+ object_ownership = "BucketOwnerPreferred"
+ }
+
+ depends_on = [aws_s3_bucket_public_access_block.website]
+}
+
+resource "aws_s3_bucket_public_access_block" "website" {
+ bucket = aws_s3_bucket.website.id
+
+ block_public_acls = false
+ block_public_policy = false
+ ignore_public_acls = false
+ restrict_public_buckets = false
+}
+
+resource "aws_s3_bucket_acl" "website" {
+ bucket = aws_s3_bucket.website.id
+
+ acl = "public-read"
+
+ depends_on = [aws_s3_bucket_ownership_controls.website]
+}
+
+resource "aws_s3_bucket_versioning" "website" {
+ bucket = aws_s3_bucket.website.id
+
+ versioning_configuration {
+ status = "Disabled"
+ }
+}
+
+resource "aws_s3_bucket_policy" "website" {
+ bucket = aws_s3_bucket.website.id
+ policy = data.aws_iam_policy_document.website.json
+}
+
+# TODO: can we restrict access to just from the CloudFront distro?
+data "aws_iam_policy_document" "website" {
+ statement {
+ sid = "AllowPublicRead"
+ effect = "Allow"
+ resources = [
+ "arn:aws:s3:::${local.bucket_name}",
+ "arn:aws:s3:::${local.bucket_name}/*",
+ ]
+ actions = ["S3:GetObject"]
+ principals {
+ type = "*"
+ identifiers = ["*"]
+ }
+ }
+
+ depends_on = [aws_s3_bucket_public_access_block.website, aws_s3_bucket.website]
+}
diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf
new file mode 100644
index 0000000..224ada4
--- /dev/null
+++ b/infrastructure/variables.tf
@@ -0,0 +1,28 @@
+variable "aws_access_key" {
+ type = string
+ sensitive = true
+ description = "An AWS access key with permission to provision all relevant resources"
+}
+
+variable "aws_secret_key" {
+ type = string
+ sensitive = true
+ description = "The secret corresponding to the provided AWS access key"
+}
+
+variable "secret_s3_bucket_suffix" {
+ type = string
+ sensitive = true
+ description = "This string should be a long string of up to 54 random characters. It will be appended to the S3 bucket name to mitigate the risk of DDoS attacks."
+ nullable = false
+
+ validation {
+ condition = length(var.secret_s3_bucket_suffix) > 12
+ error_message = "This string should be at least 12 characters"
+ }
+
+ validation {
+ condition = length(var.secret_s3_bucket_suffix) <= 54
+ error_message = "This string should be no more than 54 characters long"
+ }
+}
diff --git a/astro.config.mjs b/website/astro.config.mjs
index e4257f9..e4257f9 100644
--- a/astro.config.mjs
+++ b/website/astro.config.mjs
diff --git a/package-lock.json b/website/package-lock.json
index 50ad631..50ad631 100644
--- a/package-lock.json
+++ b/website/package-lock.json
diff --git a/package.json b/website/package.json
index ec8cb15..ec8cb15 100644
--- a/package.json
+++ b/website/package.json
diff --git a/public/css/base.css b/website/public/css/base.css
index a22ca34..a22ca34 100644
--- a/public/css/base.css
+++ b/website/public/css/base.css
diff --git a/public/css/blog.css b/website/public/css/blog.css
index 88f201a..88f201a 100644
--- a/public/css/blog.css
+++ b/website/public/css/blog.css
diff --git a/public/css/hcard.css b/website/public/css/hcard.css
index 99431d9..99431d9 100644
--- a/public/css/hcard.css
+++ b/website/public/css/hcard.css
diff --git a/public/css/reset.css b/website/public/css/reset.css
index 5a637b3..5a637b3 100644
--- a/public/css/reset.css
+++ b/website/public/css/reset.css
diff --git a/public/images/headshot.jpg b/website/public/images/headshot.jpg
index 2ca13cf..2ca13cf 100644
--- a/public/images/headshot.jpg
+++ b/website/public/images/headshot.jpg
Binary files differ
diff --git a/public/images/headshot_large.jpg b/website/public/images/headshot_large.jpg
index 2ca13cf..2ca13cf 100644
--- a/public/images/headshot_large.jpg
+++ b/website/public/images/headshot_large.jpg
Binary files differ
diff --git a/src/components/BaseHead.astro b/website/src/components/BaseHead.astro
index e8e44ab..e8e44ab 100644
--- a/src/components/BaseHead.astro
+++ b/website/src/components/BaseHead.astro
diff --git a/src/components/BlogFeed.astro b/website/src/components/BlogFeed.astro
index b6f9d55..b6f9d55 100644
--- a/src/components/BlogFeed.astro
+++ b/website/src/components/BlogFeed.astro
diff --git a/src/components/FormattedDate.astro b/website/src/components/FormattedDate.astro
index bb7a2c0..bb7a2c0 100644
--- a/src/components/FormattedDate.astro
+++ b/website/src/components/FormattedDate.astro
diff --git a/src/components/Me.astro b/website/src/components/Me.astro
index c9af587..c9af587 100644
--- a/src/components/Me.astro
+++ b/website/src/components/Me.astro
diff --git a/src/consts.ts b/website/src/consts.ts
index 461043d..461043d 100644
--- a/src/consts.ts
+++ b/website/src/consts.ts
diff --git a/src/content/blog/2024/01/14/sapiens_on_religion.md b/website/src/content/blog/2024/01/14/sapiens_on_religion.md
index b484557..b484557 100644
--- a/src/content/blog/2024/01/14/sapiens_on_religion.md
+++ b/website/src/content/blog/2024/01/14/sapiens_on_religion.md
diff --git a/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md b/website/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md
index 8641a5d..8641a5d 100644
--- a/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md
+++ b/website/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md
diff --git a/src/content/blog/2024/03/30/easter.md b/website/src/content/blog/2024/03/30/easter.md
index 9735220..9735220 100644
--- a/src/content/blog/2024/03/30/easter.md
+++ b/website/src/content/blog/2024/03/30/easter.md
diff --git a/src/content/config.ts b/website/src/content/config.ts
index 58dbc00..58dbc00 100644
--- a/src/content/config.ts
+++ b/website/src/content/config.ts
diff --git a/src/env.d.ts b/website/src/env.d.ts
index acef35f..acef35f 100644
--- a/src/env.d.ts
+++ b/website/src/env.d.ts
diff --git a/src/layouts/BlogPost.astro b/website/src/layouts/BlogPost.astro
index b709356..b709356 100644
--- a/src/layouts/BlogPost.astro
+++ b/website/src/layouts/BlogPost.astro
diff --git a/src/layouts/Page.astro b/website/src/layouts/Page.astro
index 4e5f3bb..4e5f3bb 100644
--- a/src/layouts/Page.astro
+++ b/website/src/layouts/Page.astro
diff --git a/src/pages/blog/[...slug].astro b/website/src/pages/blog/[...slug].astro
index 800c534..800c534 100644
--- a/src/pages/blog/[...slug].astro
+++ b/website/src/pages/blog/[...slug].astro
diff --git a/src/pages/blog/index.astro b/website/src/pages/blog/index.astro
index 0c617bd..0c617bd 100644
--- a/src/pages/blog/index.astro
+++ b/website/src/pages/blog/index.astro
diff --git a/src/pages/error.astro b/website/src/pages/error.astro
index fc84122..fc84122 100644
--- a/src/pages/error.astro
+++ b/website/src/pages/error.astro
diff --git a/src/pages/index.astro b/website/src/pages/index.astro
index 74cf934..74cf934 100644
--- a/src/pages/index.astro
+++ b/website/src/pages/index.astro
diff --git a/src/pages/rss.xml.js b/website/src/pages/rss.xml.js
index 79da596..79da596 100644
--- a/src/pages/rss.xml.js
+++ b/website/src/pages/rss.xml.js
diff --git a/src/content/blog/2024/04/10/tracking_pixels.md b/website/src/src/content/blog/2024/04/10/tracking_pixels.md
index 15ccc55..15ccc55 100644
--- a/src/content/blog/2024/04/10/tracking_pixels.md
+++ b/website/src/src/content/blog/2024/04/10/tracking_pixels.md
diff --git a/tsconfig.json b/website/tsconfig.json
index 41bbfbf..41bbfbf 100644
--- a/tsconfig.json
+++ b/website/tsconfig.json