Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add UI bucket and CDN
Browse files Browse the repository at this point in the history
jarrod-lowe committed Aug 18, 2024
1 parent 28c2628 commit f8f776b
Showing 7 changed files with 297 additions and 25 deletions.
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -34,19 +34,19 @@ dev: $(GRAPHQL_DEV) terraform-format terraform/environment/aws-dev/.apply terraf
@true

terraform/environment/aws-dev/.apply: terraform/environment/aws-dev/*.tf terraform/module/iac-roles/*.tf
./terraform/environment/aws-dev/deploy.sh $(ACCOUNT_ID) dev
AUTO_APPROVE=yes ./terraform/environment/aws-dev/deploy.sh $(ACCOUNT_ID) dev
touch $@

terraform/environment/wildsea-dev/plan.tfplan: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf terraform/environment/wildsea-dev/.terraform $(GRAPHQL_JS)
cd terraform/environment/wildsea-dev ; ../../../scripts/run-as.sh $(RO_ROLE) \
terraform plan -out=./plan.tfplan

terraform/environment/wildsea-dev/.apply: terraform/environment/wildsea-dev/plan.tfplan $(GRAPHQL_JS)
cd terraform/environment/wildsea-dev ; ../../../scripts/run-as.sh $(RW_ROLE) \
terraform apply ./plan.tfplan ; \
status=$$? ; \
rm -f $< ; \
[ "$$status" -eq 0 ]
cd terraform/environment/wildsea-dev ; \
../../../scripts/run-as.sh $(RW_ROLE) \
terraform apply ./plan.tfplan || status=$$? ; \
rm -v ./plan.tfplan ; \
[ -z "$$status" ] || exit $$status
touch $@

terraform/environment/wildsea-dev/.terraform: terraform/environment/wildsea-dev/*.tf terraform/module/wildsea/*.tf
9 changes: 4 additions & 5 deletions graphql/graphql.mk
Original file line number Diff line number Diff line change
@@ -24,17 +24,16 @@ graphql: $(GRAPHQL_JS) graphql-test
.PHONY: graphql-test
graphql-test: graphql/node_modules
if [ -z "$(IN_PIPELINE)" ] ; then \
docker run --rm -it --user $$(id -u):$$(id -g) -v $(PWD)/graphql:/app -w /app --entrypoint ./node_modules/jest/bin/jest.js node:20 \
docker run --rm -it --user $$(id -u):$$(id -g) -v $(PWD)/graphql:/app -w /app --entrypoint ./node_modules/jest/bin/jest.js node:20 ; \
else \
cd graphql && jest ; \
cd graphql && ./node_modules/jest/bin/jest.js ; \
fi

# Won't auto-fix in pipeline
.PHONY: graphql-eslint
graphql-eslint: $(GRAPHQL_TS)
if [ -z "$(IN_PIPELINE)" ] ; then \
docker run --rm -it --user $$(id -u):$$(id -g) -v $(PWD)/graphql:/code pipelinecomponents/eslint eslint --fix
docker run --rm -it --user $$(id -u):$$(id -g) -v $(PWD)/graphql:/code pipelinecomponents/eslint eslint --fix ; \
else \
cd graphql && eslint ;:w
\
cd graphql && eslint ; \
fi
2 changes: 1 addition & 1 deletion terraform/environment/aws/deploy.sh
Original file line number Diff line number Diff line change
@@ -42,6 +42,6 @@ terraform init \
-backend-config="key=${ENVIRONMENT}/aws.tfstate" \
-backend-config="region=${AWS_REGION}"

terraform apply \
terraform apply ${AUTO_APPROVE:+-auto-approve} \
-var environment="${ENVIRONMENT}" \
-var state_bucket="${STATE_BUCKET}"
64 changes: 51 additions & 13 deletions terraform/module/iac-roles/policy.tf
Original file line number Diff line number Diff line change
@@ -5,17 +5,20 @@ data "aws_iam_policy_document" "ro" {
"s3:GetObject"
]
resources = [
"${var.state_bucket_arn}/${var.environment}/terraform.tfstate"
"${var.state_bucket_arn}/${var.environment}/terraform.tfstate",
]
}

statement {
sid = "ListState"
actions = [
"s3:ListBucket"
"s3:ListBucket",
"s3:GetBucket*",
"s3:Get*Configuration",
]
resources = [
var.state_bucket_arn
var.state_bucket_arn,
"arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-ui",
]
}

@@ -43,12 +46,17 @@ data "aws_iam_policy_document" "ro" {
}

statement {
sid = "CognitoIdpGlobal"
sid = "Global"
actions = [
"cognito-idp:DescribeUserPoolDomain",
"wafv2:GetWebACLForResource",
"wafv2:GetWebAcl",
"appsync:GetResolver",
"cloudfront:List*",
"cloudfront:Get*Policy",
"cloudfront:GetDistribution",
"cloudfront:GetOriginAccessControl",
"iam:SimulatePrincipalPolicy",
]
resources = [
"*"
@@ -157,9 +165,11 @@ data "aws_iam_policy_document" "rw" {
"dynamodb:TagResource",
"dynamodb:UntagResource",
"dynamodb:Update*",
"s3:Put*Configuration",
]
resources = [
"arn:${data.aws_partition.current.id}:dynamodb:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:table/${var.app_name}-${var.environment}"
"arn:${data.aws_partition.current.id}:dynamodb:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:table/${var.app_name}-${var.environment}",
"arn:${data.aws_partition.current.id}:s3:::${lower(local.prefix)}-*",
]
}

@@ -188,7 +198,7 @@ data "aws_iam_policy_document" "rw" {
}

statement {
sid = "CognitoIdentityGlobal"
sid = "Global"
actions = [
"cognito-identity:CreateIdentityPool",
"cognito-identity:SetIdentityPoolRoles",
@@ -198,6 +208,13 @@ data "aws_iam_policy_document" "rw" {
"appsync:DeleteResolver",
"appsync:UpdateResolver",
"appsync:SetWebACL",
"s3:CreateBucket",
"cloudfront:CreateOriginAccessControl",
"cloudfront:DeleteOriginAccessControl",
"cloudfront:CreateDistribution*",
"cloudfront:UpdateDistribution",
"cloudfront:DeleteDistribution",
"cloudfront:TagResource",
]
resources = [
"*"
@@ -254,7 +271,7 @@ data "aws_iam_policy_document" "rw" {
"appsync:UntagResource",
]
resources = [
"arn:${data.aws_partition.current.id}:appsync:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:*"
"arn:${data.aws_partition.current.id}:appsync:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:*",
]
condition {
test = "StringEquals"
@@ -270,9 +287,12 @@ data "aws_iam_policy_document" "rw" {
"logs:TagResource",
"logs:UntagResource",
"logs:PutRetentionPolicy",
"s3:DeleteBucket",
"s3:PutBucket*",
]
resources = [
"arn:${data.aws_partition.current.id}:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:*"
"arn:${data.aws_partition.current.id}:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:*",
"arn:${data.aws_partition.current.id}:s3:::${lower(local.prefix)}-*",
]
}

@@ -335,18 +355,21 @@ data "aws_iam_policy_document" "rw_boundary" {
]
resources = [
"${var.state_bucket_arn}/${var.environment}/terraform.tfstate",
"arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-*/*"
"arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-*/*",
]
}

statement {
sid = "ListState"
actions = [
"s3:ListBucket"
"s3:ListBucket",
"s3:GetBucket*",
"s3:Get*Configuration",
"s3:Put*Configuration",
]
resources = [
"${var.state_bucket_arn}/${var.environment}/terraform.tfstate",
"arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-*/*"
"arn:${data.aws_partition.current.id}:s3:::${lower(var.app_name)}-${var.environment}-*",
]
}

@@ -397,6 +420,18 @@ data "aws_iam_policy_document" "rw_boundary" {
"appsync:DeleteResolver",
"appsync:UpdateResolver",
"appsync:GetResolver",
"s3:CreateBucket",
"cloudfront:List*",
"cloudfront:Get*Policy",
"cloudfront:CreateOriginAccessControl",
"cloudfront:GetOriginAccessControl",
"cloudfront:DeleteOriginAccessControl",
"cloudfront:CreateDistribution*",
"cloudfront:UpdateDistribution",
"cloudfront:DeleteDistribution",
"cloudfront:TagResource",
"cloudfront:GetDistribution",
"iam:SimulatePrincipalPolicy",
]
resources = [
"*"
@@ -487,7 +522,7 @@ data "aws_iam_policy_document" "rw_boundary" {
"appsync:GetGraphqlApi",
]
resources = [
"arn:${data.aws_partition.current.id}:appsync:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:*"
"arn:${data.aws_partition.current.id}:appsync:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:*",
]
condition {
test = "StringEquals"
@@ -505,9 +540,12 @@ data "aws_iam_policy_document" "rw_boundary" {
"logs:PutRetentionPolicy",
"logs:DescribeLogGroups",
"logs:ListTagsForResource",
"s3:DeleteBucket",
"s3:PutBucket*",
]
resources = [
"arn:${data.aws_partition.current.id}:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:*"
"arn:${data.aws_partition.current.id}:logs:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:log-group:*",
"arn:${data.aws_partition.current.id}:s3:::${lower(local.prefix)}-*",
]
}

20 changes: 20 additions & 0 deletions terraform/module/iac-roles/sim.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Doesn't work yet - TODO
# data "aws_iam_principal_policy_simulation" "state_read" {
# action_names = [
# "s3:GetObject"
# ]
# policy_source_arn = aws_iam_role.ro.arn
# resource_arns = [
# "${var.state_bucket_arn}/${var.environment}/terraform.tfstate",
# ]
# #resource_policy_json = data.aws_iam_policy_document.ro.json
#
# depends_on = [aws_iam_policy.ro]
#
# lifecycle {
# postcondition {
# condition = self.all_allowed
# error_message = "state_read check failed: ${jsonencode(self.results)}"
# }
# }
# }
148 changes: 148 additions & 0 deletions terraform/module/wildsea/ui-bucket.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
locals {
ui = "${var.prefix}-ui"
}

resource "aws_s3_bucket" "ui" {
# checkov:skip=CKV_AWS_18:Chosen not to enable access logging yet
# checkov:skip=CKV2_AWS_62:No need for S3 events
# checkov:skip=CKV_AWS_144:No need for cross-region replication
# checkov:skip=CKV_AWS_145:AWS ley is sufficient
bucket = lower(local.ui)

tags = {
Name = local.ui
}
}

resource "aws_s3_bucket_policy" "ui" {
bucket = aws_s3_bucket.ui.id
policy = data.aws_iam_policy_document.ui.json
}

data "aws_iam_policy_document" "ui" {
statement {
sid = "CDNRead"
effect = "Allow"
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.ui.arn}/*"]
principals {
type = "Service"
identifiers = ["cloudfront.${data.aws_partition.current.dns_suffix}"]
}
condition {
test = "StringEquals"
variable = "aws:SourceArn"
values = [aws_cloudfront_distribution.cdn.arn]
}
}

statement {
sid = "CDNList"
effect = "Allow"
actions = ["s3:ListBucket"]
resources = [aws_s3_bucket.ui.arn]
principals {
type = "Service"
identifiers = ["cloudfront.${data.aws_partition.current.dns_suffix}"]
}
condition {
test = "StringEquals"
variable = "aws:SourceArn"
values = [aws_cloudfront_distribution.cdn.arn]
}
}

# Block all HTTP Access
statement {
sid = "BlockHTTP"
effect = "Deny"
actions = ["s3:*"]
resources = [
aws_s3_bucket.ui.arn,
"${aws_s3_bucket.ui.arn}/*",
]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
}
}

resource "aws_s3_bucket_public_access_block" "ui" {
bucket = aws_s3_bucket.ui.id

block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

resource "aws_s3_bucket_ownership_controls" "ui" {
bucket = aws_s3_bucket.ui.id

rule {
object_ownership = "BucketOwnerEnforced"
}
}

resource "aws_s3_bucket_versioning" "ui" {
bucket = aws_s3_bucket.ui.id

versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "ui" {
bucket = aws_s3_bucket.ui.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

# Use recommended CORS rules for bucket behind cloudfront
resource "aws_s3_bucket_cors_configuration" "ui" {
bucket = aws_s3_bucket.ui.id

cors_rule {
allowed_headers = ["*"]
allowed_methods = ["GET", "HEAD"]
allowed_origins = ["https://${aws_cloudfront_distribution.cdn.domain_name}"]
expose_headers = ["ETag"]
max_age_seconds = 3000
}
}

# Lifecycle to:
# * Delete incomplete multipart uploads after 3 days
# * Limit old versions to maximum of 5 or and age of 30 days
# * Keep the current version forever
resource "aws_s3_bucket_lifecycle_configuration" "ui" {
bucket = aws_s3_bucket.ui.id

rule {
id = "expire-versions"
status = "Enabled"

abort_incomplete_multipart_upload {
days_after_initiation = 3
}

noncurrent_version_expiration {
newer_noncurrent_versions = 5
noncurrent_days = 30
}

expiration {
expired_object_delete_marker = true
}
}
}
67 changes: 67 additions & 0 deletions terraform/module/wildsea/ui-cdn.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
locals {
cdn_name = "${var.prefix}-cdn"
}

resource "aws_cloudfront_origin_access_control" "cdn" {
name = local.cdn_name
description = "CDN for UI"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}

data "aws_cloudfront_cache_policy" "cache_policy" {
name = "Managed-CachingOptimized"
}

data "aws_cloudfront_origin_request_policy" "request_policy" {
name = "Managed-CORS-S3Origin"
}

data "aws_cloudfront_response_headers_policy" "headers_policy" {
name = "Managed-CORS-with-preflight-and-SecurityHeadersPolicy"
}

resource "aws_cloudfront_distribution" "cdn" {
# checkov:skip=CKV2_AWS_42:Not set up custom domain yet
# checkov:skip=CKV_AWS_86:Chosen not to enable access logging yet
# checkov:skip=CKV2_AWS_47:Log4j is irrelvant for S3 origins
# checkov:skip=CKV_AWS_310:Not enabling origin failover for S3 origin
# checkov:skip=CKV_AWS_68:Not enabled WAF yet - $$$
origin {
domain_name = aws_s3_bucket.ui.bucket_regional_domain_name
origin_id = aws_s3_bucket.ui.id
origin_access_control_id = aws_cloudfront_origin_access_control.cdn.id
}

enabled = true
default_root_object = "index.html"
#aliases - TODO Use our own DNS name

default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.ui.id
cache_policy_id = data.aws_cloudfront_cache_policy.cache_policy.id
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.request_policy.id
viewer_protocol_policy = "redirect-to-https"
response_headers_policy_id = data.aws_cloudfront_response_headers_policy.headers_policy.id
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
#minimum_protocol_version = "TLSv1.2_2021" # Not available on default certificate
}

#TODO - logging

tags = {
Name = local.cdn_name
}
}

0 comments on commit f8f776b

Please sign in to comment.