Give the ECS task role project-scoped S3 and Cognito access - #242
Merged
Merged
Conversation
Contributor
|
Terraform plan in terraform Plan: 0 to add, 10 to change, 0 to destroy.Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
!~ update in-place
Terraform will perform the following actions:
# module.civic-tech-index.module.backend_prod_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/civic-tech-index-backend-prod-task-policy"
name = "civic-tech-index-backend-prod-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "civic-tech-index"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "civic-tech-index"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.civic-tech-index.module.backend_stage_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/civic-tech-index-backend-stage-task-policy"
name = "civic-tech-index-backend-stage-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "civic-tech-index"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "civic-tech-index"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.civic-tech-jobs.module.civic_tech_jobs_fullstack_stage_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/civic-tech-jobs-fullstack-stage-task-policy"
name = "civic-tech-jobs-fullstack-stage-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "civic-tech-jobs"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "civic-tech-jobs"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.home-unite-us.module.prod_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/home-unite-us-fullstack-prod-task-policy"
name = "home-unite-us-fullstack-prod-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "home-unite-us"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "home-unite-us"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.home-unite-us.module.qa_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/home-unite-us-fullstack-qa-task-policy"
name = "home-unite-us-fullstack-qa-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "home-unite-us"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "home-unite-us"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.people-depot.module.backend_dev_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/people-depot-backend-dev-task-policy"
name = "people-depot-backend-dev-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "people-depot"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "people-depot"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.vrms.module.backend_dev_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/vrms-backend-dev-task-policy"
name = "vrms-backend-dev-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.vrms.module.backend_prod_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/vrms-backend-prod-task-policy"
name = "vrms-backend-prod-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.vrms.module.frontend_dev_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/vrms-frontend-dev-task-policy"
name = "vrms-frontend-dev-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
# module.vrms.module.frontend_prod_service.aws_iam_policy.container_policy will be updated in-place
!~ resource "aws_iam_policy" "container_policy" {
id = "arn:aws:iam::035866691871:policy/vrms-frontend-prod-task-policy"
name = "vrms-frontend-prod-task-policy"
!~ policy = jsonencode(
!~ {
!~ Statement = [
!~ {
+ Sid = "EcsExecuteCommand"
# (3 unchanged attributes hidden)
},
+ {
+ Action = [
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:PutObject",
+ "s3:DeleteObject",
]
+ Condition = {
+ Null = {
+ "aws:ResourceTag/project" = "false"
}
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::*",
+ "arn:aws:s3:::*/*",
]
+ Sid = "ProjectBucketAccess"
},
+ {
+ Action = [
+ "cognito-idp:AdminGetUser",
+ "cognito-idp:AdminCreateUser",
+ "cognito-idp:AdminAddUserToGroup",
+ "cognito-idp:AdminDeleteUser",
]
+ Condition = {
+ StringEquals = {
+ "aws:ResourceTag/project" = "vrms"
}
}
+ Effect = "Allow"
+ Resource = "arn:aws:cognito-idp:us-west-2:035866691871:userpool/*"
+ Sid = "ProjectCognitoAdmin"
},
]
# (1 unchanged attribute hidden)
}
)
tags = {}
# (7 unchanged attributes hidden)
}
Plan: 0 to add, 10 to change, 0 to destroy.✅ Plan applied in Terraform apply (OIDC) #94 |
This was referenced Sep 18, 2026
ale210
added a commit
that referenced
this pull request
Sep 18, 2026
Closes #205. The documentation itself is the wiki page [Container Permissions](https://github.com/hackforla/incubator/wiki/Container-Permissions), published in wiki commit `26db1b4`. A wiki page is not delivered as a pull request, so this PR is only the last action item: linking it from the `container` module, whose `task_role_arn` output description said "Good for setting up permissions like s3 access" without saying how. ## The change One line in `terraform/modules/container/outputs.tf`. The new description names the task role as the place project-specific permissions go and links the page. The `terraform-docs` job regenerates the module README from it. ## What the wiki page covers Written for project teams rather than for DevOps: - **The two roles and which one you want** — task role vs execution role, including the trap that `incubator-prod-ecs-task-role` is an *execution* role despite the name. No running container uses that role any more; it survives only until `use_own_execution_role` is removed. - **What the task role already grants** — `ecs execute-command`, S3 by project tag and Cognito by project tag, all three delivered by #242. This section exists so nobody files a request for access they already have. - **A walkthrough for giving a container an S3 bucket**, which after #242 needs no policy change at all — just the bucket, the `project` tag and `aws_s3_bucket_abac`. The page is explicit that omitting the ABAC opt-in fails silently. - **How to ask for a permission that is not covered**, with an SQS statement as the worked example, the `aws:ResourceTag` pattern, and the two exceptions from the tag standard: services that do not support resource-level permissions or tag conditions, and actions like `ecr:GetAuthorizationToken` that cannot be scoped at all. - **How to file the request** — `hackforla/incubator`, Blank Issue Form with No Dependency, with a warning that the picker is still cluttered with website-inherited templates (#146). ## Verification Both HCL snippets on the page `terraform validate` clean against the pinned Terraform 1.16.0 and AWS provider 6.64.0, and are `fmt`-clean — `aws_s3_bucket_abac` in particular takes `abac_status` as a nested block rather than an attribute, which is easy to get wrong from prose. All external links on the page return 200, and the rendered page was checked live: headings, the role comparison table and both code blocks all render. The markdown link in the output description was tested against the pinned terraform-docs v0.20.0 before committing. No existing generated description in this repo contains a markdown link, so there was no precedent for whether the brackets would be escaped into a dead string in the README table; they are not, and the link renders clickable. ## Note `terraform fmt` would also align the four `value =` lines in this file with their `description =` lines. That misalignment predates this change and is left alone rather than adding whitespace-only churn to a one-line documentation change. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #204.
Adds two statements to the ECS task role's policy in
modules/container, both scoped by the resource'sprojecttag per decision 5 of DR-Machine-to-machine-IAM-scoping:ProjectBucketAccess—s3:ListBucket,s3:GetObject,s3:PutObject,s3:DeleteObjecton the project's own buckets.ProjectCognitoAdmin—cognito-idp:AdminGetUser,AdminCreateUser,AdminAddUserToGroup,AdminDeleteUseron the project's own user pools.This is the task role (
aws_iam_role.instance), not the execution role — the identity application code runs as.What to look for in the plan
Ten
aws_iam_policy.<project>-<app>-<env>-task-policyresources updated in place. No adds, no destroys, no replacements.descriptionis deliberately left as""because it is ForceNew onaws_iam_policyand changing it would recreate all ten.The existing
ssmmessagesstatement is restyled to the unquoted HCL form used byexecution_policybelow it and given aSid. That is cosmetic in IAM terms but does change the rendered policy document, so it shows up in the diff.Why
aws:ResourceTagand nots3:BucketTag#194 left this open. Both work, and on an object ARN both read the bucket's tags. They differ only when access points are involved, and this account has none.
aws:ResourceTagmatches the decision record and the existingEcrPullProjectImagesstatement, so consistency decides it.The S3 half grants nothing yet, by design
There is no per-project application bucket in the account today. S3 does not evaluate tag conditions against a bucket that has not opted in to ABAC, so
ProjectBucketAccessis inert until a bucket is created, tagged, and given anaws_s3_bucket_abacresource. The module's header comment now documents those three steps, and that prose is injected into the README by theterraform-docsjob.ABAC was deliberately not enabled on any existing bucket. The two Terraform state buckets and two CloudTrail log buckets belong to no single project and correctly carry no
projecttag — leaving ABAC off is what keeps them out of reach.civictechindex.orgis tagged, but it is a public static-website bucket that no container reads or writes, so opting it in would change how its tags are managed for no benefit.Terraform can still manage tags after ABAC is enabled
The ticket asked for this to be checked before enabling ABAC anywhere. The provider handles it:
aws_s3_bucketuses the S3 ControlTagResource/UntagResourceAPIs when the caller holdss3:TagResource,s3:UntagResourceands3:ListTagsForResource(#45251). Simulated against a live bucket,incubator-tf-applyis allowed all three andincubator-tf-planis allowed the read it needs. Nothing to grant.Verification
The rendered policy was put through the IAM policy simulator:
project=vrms, bucket and object ARNsprojecttag in context — an untagged bucket, or one with ABAC off — against the realhfla-incubator-terraform-stateARNproject=vrmsThe simulator uses supplied tag values, so this proves the policy logic rather than live tag state; live tags were checked separately with
list-tags-for-resource.terraform validatepasses on the pinned 1.16.0 / provider 6.64.0, andterraform fmtreports no diff on the new code.Post-merge
The negative test in the issue cannot be run from a branch and is an action item on #204 after this merges.
Worth knowing for whoever runs it: this changes nothing observable for Home Unite Us on its own. Both HUU environments pass
COGNITO_ACCESS_ID/COGNITO_ACCESS_KEYas empty strings, but the deployed image (homeuniteus:2dc864ae50.20250220-172003, Feb 2025) predates the fallback that reaches for the task role when they are empty. That fallback is on HUUmainbut is not in the running image, so the Cognito grant takes effect only once that application ships a rebuild.