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}-*" } 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}"] } } } 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 { sid = var.environment == "tf-poc" ? "DescribeDeploymentResources" : null effect = "Allow" actions = [ "autoscaling:Describe*", "ec2:Describe*", "elasticbeanstalk:DescribeApplicationVersions", "elasticbeanstalk:DescribeEnvironments", "elasticbeanstalk:DescribeEvents", "elasticloadbalancing:Describe*", ] resources = ["*"] } statement { sid = var.environment == "tf-poc" ? "CreateApplicationVersion" : null 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 { sid = var.environment == "tf-poc" ? "UpdatePocEnvironment" : null effect = "Allow" actions = ["elasticbeanstalk:UpdateEnvironment"] resources = [local.environment_arn] } dynamic "statement" { for_each = var.environment != "tf-poc" ? [1] : [] content { 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}/*", ] } } dynamic "statement" { for_each = var.environment != "tf-poc" ? [1] : [] content { 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-*"] } } dynamic "statement" { for_each = local.use_legacy_s3_policy ? [] : [1] content { sid = var.environment == "tf-poc" ? "UploadApplicationVersion" : null effect = "Allow" actions = ["s3:PutObject"] resources = ["arn:aws:s3:::${local.eb_bucket_name}/${var.eb_application_name}/*"] } } dynamic "statement" { for_each = local.use_legacy_s3_policy ? [] : [1] content { sid = var.environment == "tf-poc" ? "UseBeanstalkBucket" : null effect = "Allow" actions = ["s3:GetBucketLocation", "s3:ListBucket"] resources = ["arn:aws:s3:::${local.eb_bucket_name}"] } } dynamic "statement" { for_each = var.environment == "tf-poc" ? [1] : [] content { sid = "DenyLiveEnvironments" effect = "Deny" actions = ["elasticbeanstalk:*"] resources = [ "arn:aws:elasticbeanstalk:${var.aws_region}:${var.aws_account_id}:environment/${var.eb_application_name}/shoc-backend-dev", "arn:aws:elasticbeanstalk:${var.aws_region}:${var.aws_account_id}:environment/${var.eb_application_name}/shoc-backend-staging", ] } } } 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 } } 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 }, ], 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" { # Null VCS plans omit this Optional+Computed argument, so the provider # refreshes the live label without reverting releases. Application-CD runs # pass an immutable -- value as a run-specific # TF_VAR_release_version_label. name = var.eb_environment_name application = var.eb_application_name platform_arn = var.platform_arn version_label = var.release_version_label 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, ] } } 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 = [aws_elastic_beanstalk_environment.this.endpoint_url] lifecycle { prevent_destroy = true } }