mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 13:53:12 +00:00
HCP tried to replace the live parameters on refresh because those attributes were computed. The apply role cannot DeleteParameter.
407 lines
10 KiB
HCL
407 lines
10 KiB
HCL
locals {
|
|
bucket_arn = "arn:aws:s3:::${var.bucket_name}"
|
|
distribution_arn = "arn:aws:cloudfront::${var.aws_account_id}:distribution/${var.distribution_id}"
|
|
resource_tags = var.adoption_complete ? var.ownership_tags : var.pre_adoption_tags
|
|
bucket_tags = var.adoption_complete ? var.ownership_tags : var.pre_adoption_bucket_tags
|
|
deploy_role_tags = var.adoption_complete ? var.post_adoption_deploy_role_tags : var.pre_adoption_deploy_role_tags
|
|
ssm_prefix = "/shoc-frontend-new/${var.environment}"
|
|
github_subject = "repo:${var.github_repo}:environment:${var.environment}"
|
|
|
|
spa_rewrite_code = join("\n", [
|
|
"function handler(event) {",
|
|
" var request = event.request;",
|
|
" var uri = request.uri;",
|
|
" // No file extension after the last slash -> a client-side route.",
|
|
" if (uri.lastIndexOf('.') <= uri.lastIndexOf('/')) {",
|
|
" request.uri = '/index.html';",
|
|
" }",
|
|
" return request;",
|
|
"}",
|
|
])
|
|
}
|
|
|
|
data "aws_iam_policy_document" "site_bucket" {
|
|
dynamic "statement" {
|
|
for_each = var.adoption_complete ? [] : [1]
|
|
|
|
content {
|
|
effect = "Allow"
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = [var.bucket_auto_delete_helper_role_arn]
|
|
}
|
|
|
|
actions = [
|
|
"s3:DeleteObject*",
|
|
"s3:GetBucket*",
|
|
"s3:List*",
|
|
"s3:PutBucketPolicy",
|
|
]
|
|
resources = [
|
|
local.bucket_arn,
|
|
"${local.bucket_arn}/*",
|
|
]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
effect = "Allow"
|
|
|
|
principals {
|
|
type = "Service"
|
|
identifiers = ["cloudfront.amazonaws.com"]
|
|
}
|
|
|
|
actions = ["s3:GetObject"]
|
|
resources = ["${local.bucket_arn}/*"]
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "AWS:SourceArn"
|
|
values = [local.distribution_arn]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
effect = "Deny"
|
|
|
|
principals {
|
|
type = "AWS"
|
|
identifiers = ["*"]
|
|
}
|
|
|
|
actions = ["s3:*"]
|
|
resources = [
|
|
local.bucket_arn,
|
|
"${local.bucket_arn}/*",
|
|
]
|
|
|
|
condition {
|
|
test = "Bool"
|
|
variable = "aws:SecureTransport"
|
|
values = ["false"]
|
|
}
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "github_deploy_assume" {
|
|
statement {
|
|
sid = "GithubDeployOidc"
|
|
effect = "Allow"
|
|
actions = ["sts:AssumeRoleWithWebIdentity"]
|
|
|
|
principals {
|
|
type = "Federated"
|
|
identifiers = [var.github_oidc_provider_arn]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "token.actions.githubusercontent.com:aud"
|
|
values = ["sts.amazonaws.com"]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "token.actions.githubusercontent.com:sub"
|
|
values = [local.github_subject]
|
|
}
|
|
|
|
# StringLike: a release-triggered job loads the workflow file from the tag,
|
|
# so job_workflow_ref ends in @refs/tags/vX.Y.Z-staging there and
|
|
# @refs/heads/main on push and workflow_dispatch. The environment claim in
|
|
# sub is the gate.
|
|
condition {
|
|
test = "StringLike"
|
|
variable = "token.actions.githubusercontent.com:job_workflow_ref"
|
|
values = [
|
|
"${var.github_repo}/.github/workflows/deploy-web.yaml@refs/heads/main",
|
|
"${var.github_repo}/.github/workflows/deploy-web.yaml@refs/tags/v*",
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "github_deploy" {
|
|
statement {
|
|
sid = "ListWebBucket"
|
|
effect = "Allow"
|
|
actions = [
|
|
"s3:GetBucketLocation",
|
|
"s3:ListBucket",
|
|
]
|
|
resources = [local.bucket_arn]
|
|
}
|
|
|
|
statement {
|
|
sid = "SyncWebBucket"
|
|
effect = "Allow"
|
|
actions = [
|
|
"s3:GetObject",
|
|
"s3:PutObject",
|
|
"s3:DeleteObject",
|
|
]
|
|
resources = ["${local.bucket_arn}/*"]
|
|
}
|
|
|
|
statement {
|
|
sid = "InvalidateDistribution"
|
|
effect = "Allow"
|
|
actions = [
|
|
"cloudfront:CreateInvalidation",
|
|
"cloudfront:GetInvalidation",
|
|
"cloudfront:GetDistribution",
|
|
]
|
|
resources = [local.distribution_arn]
|
|
}
|
|
|
|
statement {
|
|
sid = "DeployParams"
|
|
effect = "Allow"
|
|
actions = [
|
|
"ssm:GetParameter",
|
|
]
|
|
resources = [
|
|
aws_ssm_parameter.deploy_bucket.arn,
|
|
aws_ssm_parameter.deploy_distribution_id.arn,
|
|
]
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket" "site" {
|
|
bucket = var.bucket_name
|
|
force_destroy = false
|
|
tags = local.bucket_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_public_access_block" "site" {
|
|
bucket = aws_s3_bucket.site.id
|
|
|
|
block_public_acls = true
|
|
block_public_policy = true
|
|
ignore_public_acls = true
|
|
restrict_public_buckets = true
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_ownership_controls" "site" {
|
|
bucket = aws_s3_bucket.site.id
|
|
|
|
rule {
|
|
object_ownership = "BucketOwnerEnforced"
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_server_side_encryption_configuration" "site" {
|
|
bucket = aws_s3_bucket.site.id
|
|
|
|
rule {
|
|
apply_server_side_encryption_by_default {
|
|
sse_algorithm = "AES256"
|
|
}
|
|
|
|
bucket_key_enabled = false
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_versioning" "site" {
|
|
bucket = aws_s3_bucket.site.id
|
|
|
|
versioning_configuration {
|
|
status = "Enabled"
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_policy" "site" {
|
|
bucket = aws_s3_bucket.site.id
|
|
policy = data.aws_iam_policy_document.site_bucket.json
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
# Pointer leftover from Terraform-promoted content CD. Forgotten, not destroyed.
|
|
# deploy-web.yaml syncs dist/ to the bucket root with --delete.
|
|
removed {
|
|
from = aws_s3_object.release_pointer
|
|
|
|
lifecycle {
|
|
destroy = false
|
|
}
|
|
}
|
|
|
|
resource "aws_cloudfront_origin_access_control" "site" {
|
|
name = var.origin_access_control_name
|
|
description = var.origin_access_control_description
|
|
origin_access_control_origin_type = "s3"
|
|
signing_behavior = "always"
|
|
signing_protocol = "sigv4"
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_cloudfront_function" "spa_rewrite" {
|
|
name = var.function_name
|
|
runtime = "cloudfront-js-1.0"
|
|
comment = "SPA routing: rewrite extensionless paths to /index.html"
|
|
publish = true
|
|
code = local.spa_rewrite_code
|
|
tags = local.resource_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
ignore_changes = [publish]
|
|
}
|
|
}
|
|
|
|
resource "aws_cloudfront_distribution" "site" {
|
|
aliases = [var.domain_name]
|
|
comment = "SeaHaven SHOC frontend (${var.environment})"
|
|
default_root_object = "index.html"
|
|
enabled = true
|
|
http_version = "http2and3"
|
|
is_ipv6_enabled = true
|
|
price_class = "PriceClass_100"
|
|
tags = local.resource_tags
|
|
|
|
origin {
|
|
connection_attempts = 3
|
|
connection_timeout = 10
|
|
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
|
|
origin_access_control_id = aws_cloudfront_origin_access_control.site.id
|
|
origin_id = var.origin_id
|
|
origin_path = ""
|
|
}
|
|
|
|
default_cache_behavior {
|
|
allowed_methods = ["GET", "HEAD", "OPTIONS"]
|
|
cache_policy_id = var.cache_policy_id
|
|
cached_methods = ["GET", "HEAD"]
|
|
compress = true
|
|
target_origin_id = var.origin_id
|
|
viewer_protocol_policy = "redirect-to-https"
|
|
|
|
function_association {
|
|
event_type = "viewer-request"
|
|
function_arn = aws_cloudfront_function.spa_rewrite.arn
|
|
}
|
|
}
|
|
|
|
restrictions {
|
|
geo_restriction {
|
|
restriction_type = "none"
|
|
}
|
|
}
|
|
|
|
viewer_certificate {
|
|
acm_certificate_arn = var.certificate_arn
|
|
minimum_protocol_version = "TLSv1.2_2021"
|
|
ssl_support_method = "sni-only"
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_route53_record" "site_a" {
|
|
zone_id = var.hosted_zone_id
|
|
name = var.domain_name
|
|
type = "A"
|
|
|
|
alias {
|
|
name = aws_cloudfront_distribution.site.domain_name
|
|
zone_id = aws_cloudfront_distribution.site.hosted_zone_id
|
|
evaluate_target_health = false
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_route53_record" "site_aaaa" {
|
|
zone_id = var.hosted_zone_id
|
|
name = var.domain_name
|
|
type = "AAAA"
|
|
|
|
alias {
|
|
name = aws_cloudfront_distribution.site.domain_name
|
|
zone_id = aws_cloudfront_distribution.site.hosted_zone_id
|
|
evaluate_target_health = false
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role" "github_deploy" {
|
|
name = var.deploy_role_name
|
|
path = "/"
|
|
description = "GitHub Actions SPA deploy role for ${var.github_repo} Environment ${var.environment}"
|
|
assume_role_policy = data.aws_iam_policy_document.github_deploy_assume.json
|
|
max_session_duration = 3600
|
|
permissions_boundary = var.deploy_permissions_boundary_arn
|
|
tags = local.deploy_role_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
# SCP ProtectDeploymentPrincipalLifecycle denies UpdateRoleDescription on
|
|
# githubdeploy-* for HCP apply roles.
|
|
ignore_changes = [description]
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "github_deploy" {
|
|
name = var.deploy_inline_policy_name
|
|
role = aws_iam_role.github_deploy.id
|
|
policy = data.aws_iam_policy_document.github_deploy.json
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "deploy_bucket" {
|
|
name = "${local.ssm_prefix}/deploy/bucket"
|
|
type = "String"
|
|
data_type = "text"
|
|
tier = "Standard"
|
|
value = aws_s3_bucket.site.id
|
|
description = "SPA origin bucket; deploy-web syncs dist/ to the bucket root"
|
|
tags = local.resource_tags
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "deploy_distribution_id" {
|
|
name = "${local.ssm_prefix}/deploy/distribution-id"
|
|
type = "String"
|
|
data_type = "text"
|
|
tier = "Standard"
|
|
value = aws_cloudfront_distribution.site.id
|
|
description = "CloudFront distribution ID; deploy-web invalidates /* after sync"
|
|
tags = local.resource_tags
|
|
}
|