-
-
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 the
secretTerraform module setting one. - 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, civictechindex. 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.
2. Shared resources carry project: shared. 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 tagged project: shared rather than left untagged. This makes "untagged" unambiguous: it means nobody has classified the resource yet, not that it is shared.
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. These are neither <project> nor shared; they are untaggable, and any audit or coverage report must have a bucket for them rather than reporting them as gaps forever.
4. Resource types that must carry the tag. 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).
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 is shared. 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.
- 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 applies
project: shared, 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.