-
-
Notifications
You must be signed in to change notification settings - Fork 10
DR Machine to machine IAM scoping
This is a record in the Decision Records on Solutions Adopted.
- Define the project tag standard #194, a child of Epic: Scope machine-to-machine IAM permissions to projects using the project tag #206.
- Unlike the earlier records here, this was not raised as a
DR: ...issue. It was written as an implementation issue inside an epic, and the decision record is one of its deliverables.
- We are about to use the
projecttag as an authorization key — machine roles in the incubator AWS account (035866691871) will be granted access to ECR, ECS, SSM, S3 and Cognito resources on the basis of that tag matching. No definition existed of what the tag's value should be, which resources must carry it, or how the value reaches a policy. - This matters more than an ordinary naming convention because tag-based access control fails closed. A resource whose tag is missing or wrong is not over-shared; it becomes unreachable. A wrong tag is an outage, not a leak.
- The live state on 2026-09-01 showed the gap is real, not theoretical:
- Five of eight ECR repositories carried their repository name as the tag value (
vrms-backend) where the project isvrms, so avrmsrole would have matched neither of its own repositories. -
0 of 37 SSM parameters carried the tag at all, despite theStruck 2026-09-09: this was wrong. All 36 parameters carried the tag, and had since it was added to thesecretTerraform module setting one.secretmodule in hackforla/incubator#140. The figure came from sweeping withaws ssm list-tags-for-resourcein Git Bash, which rewrites any argument starting with/into a Windows path, so every call failed withInvalidResourceIdand read as an untagged parameter. Corrected while auditing coverage in hackforla/incubator#197; the surviving gaps are recorded there. - None of the ten ECS services, none of the three Cognito user pools, and none of the 18 IAM roles carried it.
- Five of eight ECR repositories carried their repository name as the tag value (
1. Tag key and value. The key is project. The value is the Hack for LA project name — vrms, home-unite-us, people-depot, civic-tech-jobs, civic-tech-index. It is never an application name, an environment name, or a repository name. vrms-backend is a wrong value; vrms is the right one for both the vrms-backend and vrms-frontend repositories.
-
Changed 2026-09-13 (@ale210): the Civic Tech Index value is
civic-tech-index. The other four values are hyphenated, socivictechindexwas the odd one out. It is also the value that made a name-length problem visible: under the target group naming scheme in force when Epic: Make container resource naming consistent and safe to change #229 was written,civic-tech-index-backend-stage-tgwas 33 characters against a hard 32-character AWS limit. That is why the spelling and the container naming scheme are being fixed as one piece of work. The length constraint itself is now historical — #224 made the container module derive a target group name that always fits — but the reason the two changes travel together is not. -
civictechindexsurvives in three places that are not theprojecttag and must not be "corrected". The domaincivictechindex.org, the S3 bucket of the same name, and the ECR repositoriescivictechindex-backend-prodandcivictechindex-backend-stageuntil #225 migrates them.
2. Shared resources carry Reversed 2026-09-12 — shared resources carry no project: shared.project tag at all. Infrastructure that genuinely belongs to no single project — the ECS cluster, the load balancer, the VPC, the CloudTrail log buckets, the Terraform state buckets, ecs-ec2-role — is simply left untagged. There is no shared value.
- The original decision, adopted 2026-09-01, was that these carry
project: shared, on the reasoning that it makes "untagged" unambiguous: it would mean nobody has classified this resource yet rather than either that or it is shared. That reasoning is superseded (@ale210, 2026-09-12) and should not be re-derived. The ambiguity it warned about is real and is accepted — an untagged resource is now either shared or unclassified, and the tag alone does not distinguish them. - This does not affect how any policy is written. Because of decision 5, policies interpolate a project's value literally and never match on
shared, so no existing or planned policy referenced the retired value.
3. Some resources cannot be tagged at all, and that is a third category. AWS-managed resources such as the KMS key alias/aws/ssm accept no tags from us. They carry no project value and could not carry one if we wanted them to; they are untaggable, and any audit or coverage report must have a bucket for them rather than reporting them as gaps forever. Since decision 2 was reversed, untaggable and shared both present as an absent tag, so an audit has to separate them by resource type rather than by reading a value.
4. Resource types that must carry the tag — when the resource belongs to a project. ECR repositories, ECS services and task definitions, SSM parameters, S3 buckets, CloudWatch log groups, Cognito user pools, and the three machine IAM roles (CI/CD, ECS execution, ECS task). Since decision 2's reversal, this is a list of the types where a project-owned resource must be tagged; an instance of one of these types that belongs to no single project is correctly untagged. The shared database's two log groups are the worked example: they are of a listed type and are deliberately untagged.
5. How the project value reaches a policy: literal interpolation, not principal tags. Policies name the project literally, interpolated by Terraform:
"Condition": { "StringEquals": { "aws:ResourceTag/project": "vrms" } }
They do not self-reference the role's own tag with ${aws:PrincipalTag/project}. Three reasons:
- The scale argument for principal tags is that one policy can serve many principals. It does not apply here, because incubator already generates a set of roles and policies per project, so Terraform interpolation does the same job with nothing extra.
- It removes a dependency on principal-tag propagation, which was an untested assumption: whether a role's own tags surface as
aws:PrincipalTagwhen ECS assumes the task and execution roles. - It is more auditable. Reading a project's policy states exactly what that project can do, with no mental substitution.
6. The project tag on IAM roles is for inventory, not authorization. Because of decision 5, a missing tag on a role is a hygiene defect — it degrades inventory, cost allocation and the Terraform coverage report — but it does not break access. A missing tag on a resource does break access.
7. Exceptions, which are real and must not be rediscovered during implementation.
-
ecr:GetAuthorizationTokencannot be scoped. It is an account-level action supporting no resource-level permissions or conditions. It stays atResource: "*", and the policy should carry a comment saying why. -
S3 requires an explicit per-bucket opt-in. Attribute-based access control for general purpose buckets is disabled by default and must be enabled per bucket with
PutBucketAbac(aws s3api put-bucket-abac --bucket <name> --abac-status Status=Enabled). Until it is enabled,aws:ResourceTagands3:BucketTagconditions are not evaluated at all for that bucket, so a policy will appear to do nothing rather than fail. All five buckets in the account wereDisabledon 2026-09-01. -
Enabling S3 ABAC changes how that bucket's tags are managed.
PutBucketTaggingandDeleteBucketTaggingstop working and are replaced byTagResource/UntagResource. This must be checked against the Terraform AWS provider before enabling it on any bucket Terraform manages, or Terraform loses the ability to manage that bucket's tags. -
SSM SecureString values are encrypted under an account-wide key that cannot be tagged per project. All parameters use
alias/aws/ssm. Thekms:Decryptgrant is therefore scoped by encryption context rather than by tag: SSM passes the parameter's full ARN asPARAMETER_ARNencryption context and Parameter Store verifies it, so a role's identity policy can condition onkms:EncryptionContext:PARAMETER_ARNmatching the project's parameter path, pluskms:ViaServiceofssm.<region>.amazonaws.com.- An account-wide
kms:Decryptwould not have been a leak in any case, and this is recorded so it is not re-litigated:kms:Decrypttakes a ciphertext blob as input, not a parameter name, and the only way to obtain a SecureString's ciphertext is anssm:GetParametercall that is itself scoped. The KMS grant sits behind the SSM authorization rather than beside it. Per-project customer-managed keys were considered and are not needed.
- An account-wide
8. Known interaction, not yet resolved: the shared ECS cluster. Services are tagged per project but the cluster incubator-prod belongs to no single project and therefore carries no project tag. ecs:UpdateService acts on the service while taking the cluster as a parameter, and IAM exposes an ecs:cluster condition key. A project's CI/CD role may therefore need an explicit allowance for the shared cluster alongside its own service. This is flagged for the policy issue that implements CI/CD scoping rather than answered here.
- Adopted 2026-09-01. Agreed with @ale210 during the scoping of #206. There is no separate approval comment to cite: the decisions were made in the course of building the epic, and this page is the record of them.
- No separate feasibility analysis was performed. Instead each element was verified against the live account on 2026-09-01 before being written down:
- Tag coverage was read directly for ECR, ECS, SSM, S3, Cognito and IAM (the figures in the Problem Statement).
- S3 attribute-based access control was confirmed available, confirmed to cover object-level actions authorized by the bucket's tag, and confirmed
Disabledon all five buckets. - Cognito user pools were confirmed to support constraining IAM permissions by tag.
- The SSM encryption context
PARAMETER_ARNand the account-widealias/aws/ssmkey were confirmed against the live parameters.
- One element is a reversal of an earlier position taken during the same scoping work: the first draft proposed
${aws:PrincipalTag/project}, and decision 5 replaced it. The earlier form is not a fallback and should not be reintroduced. -
A second reversal, later and against this record rather than against a draft: decision 2 was reversed on 2026-09-12 (@ale210). The
sharedtag value is retired and shared resources carry noprojecttag. The original wording and its reasoning are kept struck through in decision 2 so the change is visible rather than silent. -
A third reversal, and the second against this record: decision 1's value for Civic Tech Index was changed on 2026-09-13 (@ale210), from
civictechindextocivic-tech-index. Raised while scoping Epic: Make container resource naming consistent and safe to change #229, which this change gates. Unlike decision 2, the superseded value is not kept struck through in the list itself; the change is recorded in the note under decision 1.
- Adopted, and implementation is tracked rather than complete. This record defines the standard; the work to make the account conform to it is the sub-issues of #206.
- Directly implementing this record: #195 corrects the ECR tag values, #196 tags ECS services and task definitions, #197 audits coverage everywhere else, and #198 and #199 tag the machine roles for inventory.
- Consuming it: #202, #203 and #204 write the scoped policies, and #205 documents how a project team requests a new permission.
- The sequencing constraint is the important part of this record. Because tag-based access control fails closed, every tagging issue above must land and be verified before the policy issues that condition on those tags.