Skip to content

DR Machine to machine IAM scoping

ale210 edited this page Sep 2, 2026 · 1 revision

This is a record in the Decision Records on Solutions Adopted.

Issue

Problem Statement

  • We are about to use the project tag 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 is vrms, so a vrms role would have matched neither of its own repositories.
    • 0 of 37 SSM parameters carried the tag at all, despite the secret Terraform 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.

Potential Solution

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:PrincipalTag when 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:GetAuthorizationToken cannot be scoped. It is an account-level action supporting no resource-level permissions or conditions. It stays at Resource: "*", 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:ResourceTag and s3:BucketTag conditions 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 were Disabled on 2026-09-01.
  • Enabling S3 ABAC changes how that bucket's tags are managed. PutBucketTagging and DeleteBucketTagging stop working and are replaced by TagResource / 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. The kms:Decrypt grant is therefore scoped by encryption context rather than by tag: SSM passes the parameter's full ARN as PARAMETER_ARN encryption context and Parameter Store verifies it, so a role's identity policy can condition on kms:EncryptionContext:PARAMETER_ARN matching the project's parameter path, plus kms:ViaService of ssm.<region>.amazonaws.com.
    • An account-wide kms:Decrypt would not have been a leak in any case, and this is recorded so it is not re-litigated: kms:Decrypt takes a ciphertext blob as input, not a parameter name, and the only way to obtain a SecureString's ciphertext is an ssm:GetParameter call 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.

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.

Feasibility Determination

  • 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 Disabled on all five buckets.
    • Cognito user pools were confirmed to support constraining IAM permissions by tag.
    • The SSM encryption context PARAMETER_ARN and the account-wide alias/aws/ssm key 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.

Summary

  • 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.

Clone this wiki locally