mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 15:23:12 +00:00
603 lines
18 KiB
HCL
603 lines
18 KiB
HCL
data "aws_iam_openid_connect_provider" "github" {
|
|
url = "https://token.actions.githubusercontent.com"
|
|
}
|
|
|
|
data "aws_elastic_beanstalk_hosted_zone" "current" {}
|
|
|
|
locals {
|
|
application_arn = "arn:aws:elasticbeanstalk:${var.aws_region}:${var.aws_account_id}:application/${var.eb_application_name}"
|
|
environment_arn = "arn:aws:elasticbeanstalk:${var.aws_region}:${var.aws_account_id}:environment/${var.eb_application_name}/${var.eb_environment_name}"
|
|
environment_stack_name = "awseb-${var.eb_environment_id}-stack"
|
|
eb_bucket_name = "elasticbeanstalk-${var.aws_region}-${var.aws_account_id}"
|
|
use_legacy_s3_policy = var.legacy_dev_s3_policy
|
|
app_config_secret_pattern = "arn:aws:secretsmanager:${var.aws_region}:${var.aws_account_id}:secret:${var.app_config_secret_name}-*"
|
|
deploy_ssm_prefix = "/shoc-backend/${var.environment}/deploy"
|
|
}
|
|
|
|
data "aws_iam_policy_document" "runtime_assume" {
|
|
statement {
|
|
effect = "Allow"
|
|
actions = ["sts:AssumeRole"]
|
|
|
|
principals {
|
|
type = "Service"
|
|
identifiers = ["ec2.amazonaws.com"]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role" "runtime" {
|
|
name = var.runtime_role_name
|
|
path = "/"
|
|
description = var.metadata_before_adoption.runtime_role_description
|
|
assume_role_policy = data.aws_iam_policy_document.runtime_assume.json
|
|
max_session_duration = 3600
|
|
permissions_boundary = var.permissions_boundary_arn
|
|
tags = var.adoption_complete ? merge(var.metadata_before_adoption.runtime_role_tags, { ManagedBy = "terraform" }) : var.metadata_before_adoption.runtime_role_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "web_tier" {
|
|
role = aws_iam_role.runtime.name
|
|
policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier"
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "runtime_app_config" {
|
|
statement {
|
|
sid = var.app_config_policy_sid
|
|
effect = "Allow"
|
|
actions = ["secretsmanager:GetSecretValue"]
|
|
resources = [local.app_config_secret_pattern]
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "runtime_app_config" {
|
|
name = var.runtime_app_config_policy_name
|
|
role = aws_iam_role.runtime.id
|
|
policy = data.aws_iam_policy_document.runtime_app_config.json
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "runtime_webhook" {
|
|
count = var.work_order_webhook_enabled ? 1 : 0
|
|
|
|
statement {
|
|
sid = var.webhook_read_policy_sid
|
|
effect = "Allow"
|
|
actions = [
|
|
"secretsmanager:DescribeSecret",
|
|
"secretsmanager:GetSecretValue",
|
|
]
|
|
resources = [var.webhook_secret_arn]
|
|
}
|
|
|
|
statement {
|
|
sid = var.webhook_decrypt_policy_sid
|
|
effect = "Allow"
|
|
actions = ["kms:Decrypt"]
|
|
resources = ["arn:aws:kms:us-east-1:011934824531:key/d10fd1f0-a61a-4405-8568-85e9fd11ba18"]
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["secretsmanager.us-east-1.amazonaws.com"]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "runtime_webhook" {
|
|
count = var.work_order_webhook_enabled ? 1 : 0
|
|
|
|
name = var.runtime_webhook_policy_name
|
|
role = aws_iam_role.runtime.id
|
|
policy = data.aws_iam_policy_document.runtime_webhook[0].json
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "runtime_dynamo" {
|
|
count = var.dynamo_reader_role_arn == null ? 0 : 1
|
|
|
|
statement {
|
|
sid = var.dynamo_policy_sid
|
|
effect = "Allow"
|
|
actions = ["sts:AssumeRole"]
|
|
resources = [var.dynamo_reader_role_arn]
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "runtime_dynamo" {
|
|
count = var.dynamo_reader_role_arn == null ? 0 : 1
|
|
|
|
name = var.runtime_dynamo_policy_name
|
|
role = aws_iam_role.runtime.id
|
|
policy = data.aws_iam_policy_document.runtime_dynamo[0].json
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_instance_profile" "runtime" {
|
|
name = var.runtime_role_name
|
|
path = "/"
|
|
role = aws_iam_role.runtime.name
|
|
tags = var.adoption_complete ? merge(var.metadata_before_adoption.instance_profile_tags, { ManagedBy = "terraform" }) : var.metadata_before_adoption.instance_profile_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_secretsmanager_secret" "app_config" {
|
|
# Terraform owns the secret shell and metadata only. Values remain out of
|
|
# band and must never be declared in this resource or its callers.
|
|
name = var.app_config_secret_name
|
|
description = var.metadata_before_adoption.app_config_description
|
|
tags = var.adoption_complete ? merge(var.metadata_before_adoption.app_config_tags, { ManagedBy = "terraform" }) : var.metadata_before_adoption.app_config_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
ignore_changes = [
|
|
force_overwrite_replica_secret,
|
|
recovery_window_in_days,
|
|
]
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "deploy_assume" {
|
|
statement {
|
|
effect = "Allow"
|
|
actions = ["sts:AssumeRoleWithWebIdentity"]
|
|
|
|
principals {
|
|
type = "Federated"
|
|
identifiers = [data.aws_iam_openid_connect_provider.github.arn]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "token.actions.githubusercontent.com:aud"
|
|
values = ["sts.amazonaws.com"]
|
|
}
|
|
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "token.actions.githubusercontent.com:sub"
|
|
values = ["repo:${var.github_repo}:environment:${var.github_environment}"]
|
|
}
|
|
|
|
# StringLike: a tag-loaded reusable workflow uses @refs/tags/v*, while
|
|
# push and workflow_dispatch use @refs/heads/main. Adding a deploy
|
|
# workflow means adding its ref here (cross-family IAM).
|
|
condition {
|
|
test = "StringLike"
|
|
variable = "token.actions.githubusercontent.com:job_workflow_ref"
|
|
values = [
|
|
"${var.github_repo}/.github/workflows/deploy.yaml@refs/heads/main",
|
|
"${var.github_repo}/.github/workflows/deploy.yaml@refs/tags/v*",
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role" "github_deploy" {
|
|
name = var.github_deploy_role_name
|
|
path = "/"
|
|
description = var.metadata_before_adoption.deploy_role_description
|
|
assume_role_policy = data.aws_iam_policy_document.deploy_assume.json
|
|
max_session_duration = 3600
|
|
permissions_boundary = var.github_deploy_permissions_boundary_arn
|
|
tags = var.adoption_complete ? merge(var.metadata_before_adoption.deploy_role_tags, { ManagedBy = "terraform" }) : var.metadata_before_adoption.deploy_role_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "deploy" {
|
|
statement {
|
|
effect = "Allow"
|
|
actions = [
|
|
"autoscaling:Describe*",
|
|
"ec2:Describe*",
|
|
"elasticbeanstalk:DescribeApplicationVersions",
|
|
"elasticbeanstalk:DescribeEnvironments",
|
|
"elasticbeanstalk:DescribeEvents",
|
|
"elasticloadbalancing:Describe*",
|
|
]
|
|
resources = ["*"]
|
|
}
|
|
|
|
statement {
|
|
effect = "Allow"
|
|
actions = ["elasticbeanstalk:CreateApplicationVersion"]
|
|
resources = [
|
|
local.application_arn,
|
|
"arn:aws:elasticbeanstalk:${var.aws_region}:${var.aws_account_id}:applicationversion/${var.eb_application_name}/*",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
effect = "Allow"
|
|
actions = ["elasticbeanstalk:UpdateEnvironment"]
|
|
resources = [local.environment_arn]
|
|
}
|
|
|
|
statement {
|
|
effect = "Allow"
|
|
actions = [
|
|
"cloudformation:CancelUpdateStack",
|
|
"cloudformation:DescribeStackEvents",
|
|
"cloudformation:DescribeStackResource",
|
|
"cloudformation:DescribeStackResources",
|
|
"cloudformation:DescribeStacks",
|
|
"cloudformation:GetTemplate",
|
|
"cloudformation:ListStackResources",
|
|
"cloudformation:UpdateStack",
|
|
]
|
|
resources = [
|
|
"arn:aws:cloudformation:${var.aws_region}:${var.aws_account_id}:stack/${local.environment_stack_name}/*",
|
|
]
|
|
}
|
|
|
|
statement {
|
|
effect = "Allow"
|
|
actions = [
|
|
"autoscaling:PutNotificationConfiguration",
|
|
"autoscaling:ResumeProcesses",
|
|
"autoscaling:SuspendProcesses",
|
|
]
|
|
resources = [
|
|
"arn:aws:autoscaling:${var.aws_region}:${var.aws_account_id}:autoScalingGroup:*:autoScalingGroupName/${local.environment_stack_name}-*",
|
|
]
|
|
}
|
|
|
|
dynamic "statement" {
|
|
for_each = local.use_legacy_s3_policy ? [1] : []
|
|
content {
|
|
effect = "Allow"
|
|
actions = ["s3:Delete*", "s3:Get*", "s3:Put*"]
|
|
resources = ["arn:aws:s3:::elasticbeanstalk-*/*"]
|
|
}
|
|
}
|
|
|
|
dynamic "statement" {
|
|
for_each = local.use_legacy_s3_policy ? [1] : []
|
|
content {
|
|
effect = "Allow"
|
|
actions = [
|
|
"s3:GetBucket*",
|
|
"s3:ListBucket",
|
|
"s3:PutBucketOwnershipControls",
|
|
"s3:PutBucketPolicy",
|
|
"s3:PutBucketPublicAccessBlock",
|
|
]
|
|
resources = ["arn:aws:s3:::elasticbeanstalk-*"]
|
|
}
|
|
}
|
|
|
|
# deploy.yaml uploads each bundle to
|
|
# <app>/releases/<environment>/<sha>/<run>/site.zip and Elastic Beanstalk
|
|
# reads it back from there. The deploy role never writes another
|
|
# environment's release prefix.
|
|
dynamic "statement" {
|
|
for_each = local.use_legacy_s3_policy ? [] : [1]
|
|
content {
|
|
sid = "UploadReleaseBundle"
|
|
effect = "Allow"
|
|
actions = [
|
|
"s3:PutObject",
|
|
"s3:GetObject",
|
|
]
|
|
resources = [
|
|
"arn:aws:s3:::${local.eb_bucket_name}/${var.eb_application_name}/releases/${var.environment}/*",
|
|
]
|
|
}
|
|
}
|
|
|
|
# Elastic Beanstalk stages the processed version, embedded extensions,
|
|
# and manifests under environment-scoped prefixes and requires object ACLs
|
|
# on this BucketOwnerPreferred bucket.
|
|
dynamic "statement" {
|
|
for_each = local.use_legacy_s3_policy ? [] : [1]
|
|
content {
|
|
sid = "ManageEnvironmentArtifacts"
|
|
effect = "Allow"
|
|
actions = [
|
|
"s3:PutObject",
|
|
"s3:PutObjectAcl",
|
|
"s3:PutObjectVersionAcl",
|
|
"s3:GetObject",
|
|
"s3:GetObjectAcl",
|
|
"s3:GetObjectVersion",
|
|
"s3:GetObjectVersionAcl",
|
|
"s3:DeleteObject",
|
|
]
|
|
resources = [
|
|
"arn:aws:s3:::${local.eb_bucket_name}/resources/_runtime/_embedded_extensions/${var.eb_application_name}/*",
|
|
"arn:aws:s3:::${local.eb_bucket_name}/resources/environments/${var.eb_environment_id}/*",
|
|
]
|
|
}
|
|
}
|
|
|
|
dynamic "statement" {
|
|
for_each = local.use_legacy_s3_policy ? [] : [1]
|
|
content {
|
|
effect = "Allow"
|
|
actions = [
|
|
"s3:GetBucketLocation",
|
|
"s3:ListBucket",
|
|
"s3:GetBucketPolicy",
|
|
"s3:GetBucketAcl",
|
|
"s3:GetBucketVersioning",
|
|
"s3:GetBucketOwnershipControls",
|
|
]
|
|
resources = ["arn:aws:s3:::${local.eb_bucket_name}"]
|
|
}
|
|
}
|
|
|
|
statement {
|
|
sid = "ReadDeployParameters"
|
|
effect = "Allow"
|
|
actions = [
|
|
"ssm:GetParameter",
|
|
"ssm:GetParameters",
|
|
]
|
|
resources = [
|
|
"arn:aws:ssm:${var.aws_region}:${var.aws_account_id}:parameter/shoc-backend/${var.environment}/deploy/*",
|
|
]
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "github_deploy" {
|
|
name = var.github_deploy_policy_name
|
|
role = aws_iam_role.github_deploy.id
|
|
policy = data.aws_iam_policy_document.deploy.json
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "deploy_application_name" {
|
|
name = "${local.deploy_ssm_prefix}/application-name"
|
|
type = "String"
|
|
value = var.eb_application_name
|
|
description = "Elastic Beanstalk application name; GitHub CD creates application versions here"
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "deploy_environment_name" {
|
|
name = "${local.deploy_ssm_prefix}/environment-name"
|
|
type = "String"
|
|
value = var.eb_environment_name
|
|
description = "Elastic Beanstalk environment name; GitHub CD calls UpdateEnvironment"
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "deploy_artifacts_bucket" {
|
|
name = "${local.deploy_ssm_prefix}/artifacts-bucket"
|
|
type = "String"
|
|
value = local.eb_bucket_name
|
|
description = "Bucket for release zips; GitHub CD uploads site.zip here"
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "deploy_smoke_url" {
|
|
name = "${local.deploy_ssm_prefix}/smoke-url"
|
|
type = "String"
|
|
value = var.smoke_url
|
|
description = "HTTPS origin for post-deploy smoke checks"
|
|
}
|
|
|
|
locals {
|
|
managed_eb_settings = concat(
|
|
[
|
|
{
|
|
namespace = "aws:elasticbeanstalk:environment"
|
|
name = "EnvironmentType"
|
|
value = "LoadBalanced"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:environment"
|
|
name = "LoadBalancerType"
|
|
value = "application"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:environment"
|
|
name = "ServiceRole"
|
|
value = var.eb_service_role_name
|
|
},
|
|
{
|
|
namespace = "aws:ec2:vpc"
|
|
name = "VPCId"
|
|
value = var.vpc_id
|
|
},
|
|
{
|
|
namespace = "aws:ec2:vpc"
|
|
name = "Subnets"
|
|
value = join(",", sort(var.instance_subnet_ids))
|
|
},
|
|
{
|
|
namespace = "aws:ec2:vpc"
|
|
name = "ELBSubnets"
|
|
value = join(",", sort(var.load_balancer_subnet_ids))
|
|
},
|
|
{
|
|
namespace = "aws:ec2:vpc"
|
|
name = "ELBScheme"
|
|
value = "public"
|
|
},
|
|
{
|
|
namespace = "aws:ec2:vpc"
|
|
name = "AssociatePublicIpAddress"
|
|
value = "true"
|
|
},
|
|
{
|
|
namespace = "aws:autoscaling:launchconfiguration"
|
|
name = "IamInstanceProfile"
|
|
value = aws_iam_instance_profile.runtime.name
|
|
},
|
|
{
|
|
namespace = "aws:autoscaling:launchconfiguration"
|
|
name = "InstanceType"
|
|
value = "t3.small"
|
|
},
|
|
{
|
|
namespace = "aws:autoscaling:asg"
|
|
name = "MinSize"
|
|
value = "1"
|
|
},
|
|
{
|
|
namespace = "aws:autoscaling:asg"
|
|
name = "MaxSize"
|
|
value = "1"
|
|
},
|
|
{
|
|
namespace = "aws:elbv2:listener:443"
|
|
name = "Protocol"
|
|
value = "HTTPS"
|
|
},
|
|
{
|
|
namespace = "aws:elbv2:listener:443"
|
|
name = "SSLCertificateArns"
|
|
value = var.shared_certificate_arn
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:environment:process:default"
|
|
name = "HealthCheckPath"
|
|
value = "/"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:environment:process:default"
|
|
name = "MatcherHTTPCode"
|
|
value = "200-499"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "ASPNETCORE_ENVIRONMENT"
|
|
value = "Production"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "ASPNETCORE_URLS"
|
|
value = "http://0.0.0.0:5000"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "WorkOrderWebhook__Enabled"
|
|
value = var.work_order_webhook_enabled ? "true" : "false"
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "WorkOrderWebhook__Region"
|
|
value = var.aws_region
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "SENTRY_DSN"
|
|
value = var.sentry_dsn
|
|
},
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "SENTRY_ENVIRONMENT"
|
|
value = var.environment == "dev" ? "development" : var.environment
|
|
},
|
|
],
|
|
var.instance_security_group_id == null ? [] : [
|
|
{
|
|
namespace = "aws:autoscaling:launchconfiguration"
|
|
name = "SecurityGroups"
|
|
value = var.instance_security_group_id
|
|
},
|
|
],
|
|
[
|
|
for key in sort(tolist(var.app_config_json_keys)) : {
|
|
namespace = "aws:elasticbeanstalk:application:environmentsecrets"
|
|
name = key
|
|
value = "${aws_secretsmanager_secret.app_config.arn}:${key}"
|
|
}
|
|
],
|
|
var.webhook_secret_arn == null ? [] : [
|
|
{
|
|
namespace = "aws:elasticbeanstalk:application:environment"
|
|
name = "WorkOrderWebhook__SecretId"
|
|
value = var.webhook_secret_arn
|
|
},
|
|
],
|
|
)
|
|
}
|
|
|
|
resource "aws_elastic_beanstalk_environment" "this" {
|
|
# GitHub Actions owns application versions via UpdateEnvironment.
|
|
# version_label is ignored so app deploys are not Terraform drift.
|
|
name = var.eb_environment_name
|
|
application = var.eb_application_name
|
|
platform_arn = var.platform_arn
|
|
tier = "WebServer"
|
|
cname_prefix = var.eb_environment_name
|
|
|
|
dynamic "setting" {
|
|
for_each = var.manage_eb_settings ? local.managed_eb_settings : []
|
|
|
|
content {
|
|
namespace = setting.value.namespace
|
|
name = setting.value.name
|
|
value = setting.value.value
|
|
}
|
|
}
|
|
|
|
tags = var.metadata_before_adoption.environment_tags
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
ignore_changes = [
|
|
wait_for_ready_timeout,
|
|
version_label,
|
|
]
|
|
}
|
|
}
|
|
|
|
resource "aws_route53_record" "api_alias" {
|
|
count = var.api_record_type == "A" ? 1 : 0
|
|
|
|
zone_id = var.hosted_zone_id
|
|
name = var.api_domain
|
|
type = "A"
|
|
|
|
alias {
|
|
name = var.api_alias_target == null ? aws_elastic_beanstalk_environment.this.cname : var.api_alias_target.name
|
|
zone_id = var.api_alias_target == null ? data.aws_elastic_beanstalk_hosted_zone.current.id : var.api_alias_target.zone_id
|
|
evaluate_target_health = true
|
|
}
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
resource "aws_route53_record" "api_cname" {
|
|
count = var.api_record_type == "CNAME" ? 1 : 0
|
|
|
|
zone_id = var.hosted_zone_id
|
|
name = var.api_domain
|
|
type = "CNAME"
|
|
ttl = 60
|
|
records = [
|
|
var.api_cname_target == null ? aws_elastic_beanstalk_environment.this.endpoint_url : var.api_cname_target,
|
|
]
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|