From e55f60a7f07baa444f2c568876415bb20fa5119b Mon Sep 17 00:00:00 2001 From: Will Freeman Date: Mon, 28 Oct 2024 22:18:29 -0600 Subject: [PATCH] use terraform, add s3-cloudfront site --- .gitignore | 6 ++ terraform/variables.tf | 8 ++ terraform/webapp.tf | 167 +++++++++++++++++++++++++++++++++++++++ webapp/package-lock.json | 75 +++++++----------- 4 files changed, 211 insertions(+), 45 deletions(-) create mode 100644 terraform/variables.tf create mode 100644 terraform/webapp.tf diff --git a/.gitignore b/.gitignore index 3162aaf..16a754a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ coverage *.tsbuildinfo project/**/target/ + +# Terraform +terraform.tfstate.d/ +.terraform/ +.terraform.lock.hcl +terraform.tfvars diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..68d8c57 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,8 @@ +variable "domain_name" { + type = string + description = "Domain name" +} +variable "bucket_name" { + type = string + description = "S3 bucket name for the static site" +} diff --git a/terraform/webapp.tf b/terraform/webapp.tf new file mode 100644 index 0000000..55e768d --- /dev/null +++ b/terraform/webapp.tf @@ -0,0 +1,167 @@ +# Provider Configuration +provider "aws" { + region = "us-east-1" # ACM certificates for CloudFront must be in us-east-1 +} + +# Route 53 Zone for Domain +resource "aws_route53_zone" "deflock_me" { + name = var.domain_name +} + +# S3 Bucket for Static Site Hosting +resource "aws_s3_bucket" "vue_app" { + bucket = var.bucket_name + + tags = { + Name = "Vue App Static Site Bucket" + } +} + +resource "aws_s3_bucket_acl" "vue_app_acl" { + bucket = aws_s3_bucket.vue_app.id + acl = "private" +} + +resource "aws_s3_bucket_website_configuration" "vue_app" { + bucket = aws_s3_bucket.vue_app.id + index_document { + suffix = "index.html" + } + error_document { + key = "index.html" + } +} + +resource "aws_s3_bucket_policy" "s3_access_policy" { + bucket = aws_s3_bucket.vue_app.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AllowPublic" + Effect = "Allow" + Principal = "*" + Action = "s3:GetObject" + Resource = "${aws_s3_bucket.vue_app.arn}/*" + }, + ] + }) +} + +# ACM Certificate for HTTPS +resource "aws_acm_certificate" "deflock_me_cert" { + domain_name = var.domain_name + validation_method = "DNS" + subject_alternative_names = [ + "www.${var.domain_name}" + ] + + lifecycle { + create_before_destroy = true + } +} + +# DNS Validation Records for ACM Certificate +resource "aws_route53_record" "deflock_me_cert_validation" { + for_each = { + for dvo in aws_acm_certificate.deflock_me_cert.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + type = dvo.resource_record_type + value = dvo.resource_record_value + zone_id = aws_route53_zone.deflock_me.zone_id + } + } + + zone_id = each.value.zone_id + name = each.value.name + type = each.value.type + ttl = 60 + records = [each.value.value] +} + +# ACM Certificate Validation Completion +resource "aws_acm_certificate_validation" "deflock_me_cert_validation" { + certificate_arn = aws_acm_certificate.deflock_me_cert.arn + validation_record_fqdns = [for record in aws_route53_record.deflock_me_cert_validation : record.fqdn] +} + +# CloudFront Distribution for CDN and HTTPS +resource "aws_cloudfront_distribution" "vue_app_cdn" { + origin { + domain_name = aws_s3_bucket.vue_app.website_endpoint + origin_id = "S3-VueApp" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["TLSv1.2"] + } + } + + enabled = true + is_ipv6_enabled = true + comment = "CDN for ${var.domain_name}" + default_root_object = "index.html" + + aliases = ["${var.domain_name}", "www.${var.domain_name}"] + + default_cache_behavior { + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "S3-VueApp" + + forwarded_values { + query_string = false + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + viewer_certificate { + acm_certificate_arn = aws_acm_certificate_validation.deflock_me_cert_validation.certificate_arn + ssl_support_method = "sni-only" + minimum_protocol_version = "TLSv1.2_2021" + } + + tags = { + Name = "CloudFront Vue App CDN" + } +} + +# Route 53 Records for Domain +resource "aws_route53_record" "deflock_me_root" { + zone_id = aws_route53_zone.deflock_me.zone_id + name = var.domain_name + type = "A" + + alias { + name = aws_cloudfront_distribution.vue_app_cdn.domain_name + zone_id = aws_cloudfront_distribution.vue_app_cdn.hosted_zone_id + evaluate_target_health = false + } +} + +resource "aws_route53_record" "deflock_me_www" { + zone_id = aws_route53_zone.deflock_me.zone_id + name = "www.${var.domain_name}" + type = "A" + + alias { + name = aws_cloudfront_distribution.vue_app_cdn.domain_name + zone_id = aws_cloudfront_distribution.vue_app_cdn.hosted_zone_id + evaluate_target_health = false + } +} diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 5e7b1e4..183f590 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -28,27 +28,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz", + "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -58,13 +58,12 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1363,14 +1362,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -1553,31 +1544,30 @@ }, "dependencies": { "@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" }, "@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" }, "@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz", + "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==", "requires": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.0" } }, "@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "requires": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" } }, "@esbuild/aix-ppc64": { @@ -2390,11 +2380,6 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, "typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",