From 4fb4159df2463b41f0eeba26c55d4696c52ae1d0 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:10:13 -0400 Subject: [PATCH] fix(iam): scope staging githubdeploy uploads to the staging release prefix (#158) --- terraform/live/README.md | 10 ++++++-- .../live/modules/environment-owned/main.tf | 24 ++++++++++++++++++- terraform/live/staging/main.tf | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/terraform/live/README.md b/terraform/live/README.md index 8f3850b..e4af673 100644 --- a/terraform/live/README.md +++ b/terraform/live/README.md @@ -15,7 +15,10 @@ shared or Elastic Beanstalk-generated infrastructure. The shared `shoc-backend` Elastic Beanstalk application and `shoc-sqlserver-shared` RDS instance, VPC, subnets, EB service role, shared certificate, shared RDS security group, and EB-generated SG/ALB/ASG/CloudFormation -resources must never enter an environment state. +resources must never enter an environment state. Both roots leave +`instance_security_group_id` null: the AWS provider reports the EB-generated +`awseb-*-AWSEBSecurityGroup` as an empty `SecurityGroups` setting, so pinning it +produces a permanent update diff. Secret values are not Terraform resources, variables, outputs, or managed EB settings. Terraform manages the app-config secret shell and maps approved JSON @@ -132,7 +135,10 @@ exact bundle it uploads; bundle bytes never enter Terraform plans or state. GitHub Actions owns application versions. It compiles the bundle, uploads it, creates the Elastic Beanstalk application version, and calls `UpdateEnvironment`. Terraform ignores `version_label` so those deploys are not -drift. If health, smoke, or the webhook probe fails after that update, the job +drift. The non-legacy deploy policy writes bundles only under +`shoc-backend/releases//*`; the Elastic Beanstalk staging +prefixes (`resources/_runtime/_embedded_extensions/shoc-backend/*` and +`resources/environments//*`) keep the full object and ACL action set. If health, smoke, or the webhook probe fails after that update, the job restores the previous Elastic Beanstalk version label. Database migrations already applied by the failed bundle are not reverted. Deploy parameters are read from `/shoc-backend//deploy/*` SSM parameters this module writes. diff --git a/terraform/live/modules/environment-owned/main.tf b/terraform/live/modules/environment-owned/main.tf index 8bf364b..713f7fa 100644 --- a/terraform/live/modules/environment-owned/main.tf +++ b/terraform/live/modules/environment-owned/main.tf @@ -289,9 +289,32 @@ data "aws_iam_policy_document" "deploy" { } } + # deploy.yaml uploads each bundle to + # /releases////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", @@ -304,7 +327,6 @@ data "aws_iam_policy_document" "deploy" { "s3:DeleteObject", ] resources = [ - "arn:aws:s3:::${local.eb_bucket_name}/${var.eb_application_name}/*", "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}/*", ] diff --git a/terraform/live/staging/main.tf b/terraform/live/staging/main.tf index cd64267..1964ad5 100644 --- a/terraform/live/staging/main.tf +++ b/terraform/live/staging/main.tf @@ -35,7 +35,7 @@ module "environment" { vpc_id = "vpc-0d16336143f3da25e" instance_subnet_ids = ["subnet-02946ccd6735742e9", "subnet-0bedaa6e0c750be4f", "subnet-09eaf2bfa468d206f"] load_balancer_subnet_ids = ["subnet-09eaf2bfa468d206f", "subnet-02946ccd6735742e9", "subnet-0bedaa6e0c750be4f"] - instance_security_group_id = "sg-02ea36a6719217fa2" + instance_security_group_id = null eb_service_role_name = "shoc-eb-service-role" shared_certificate_arn = "arn:aws:acm:us-east-1:396287094661:certificate/2b78e74f-7b65-4b82-a413-7a498b102f00" runtime_role_name = "shoc-backend-staging"