How to cover every team with one policy#

This is attribute-based access control, ABAC, applied to the resources your configuration manages.

A policy per team is a policy set that grows with the org chart and drifts between its members. ABAC exists to avoid that, and it needs an attribute on the resource to match against. Markers are one, written on every resource the estate manages rather than applied by a convention someone has to remember.

The policy#

{
  "Sid": "MutateYourOwnTeamsEstates",
  "Effect": "Allow",
  "Action": ["ec2:CreateTags", "ec2:DeleteTags", "ec2:TerminateInstances"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "aws:ResourceTag/tofu-estate": "${aws:PrincipalTag/team}"
    }
  }
}

One policy covers every team. Onboarding a team is a session tag rather than a new policy, and there is no per-team document to review, drift, or forget to revoke.

The principal tag can come from your identity provider through AssumeRoleWithSAML or AssumeRoleWithWebIdentity, so team membership is asserted where it is already managed rather than copied into AWS by hand.

Pick the naming convention deliberately#

The match is a string comparison, so estate names and team tags have to agree. Decide which direction is authoritative before you have many of either. An estate named for a system rather than a team will not match, and a team that owns several estates needs either several tags or a prefix match with StringLike.

The same shape takes any condition key#

Once the resource carries an attribute, every IAM condition applies to it.

KeyWhat it gives you
aws:CurrentTimeA change window. The estate is mutable between named hours.
aws:MultiFactorAuthPresentMFA required to touch production.
aws:SourceIpChanges only from a known network.
aws:PrincipalOrgIDOnly principals inside your organization.

A session that expires expires the access with it. None of these can be expressed against a state file, because a file has no attributes for a condition to read.

Where it stops#

The condition is only evaluated where AWS evaluates it, and a resource can only carry a marker if its type takes tags. Where AWS honours the condition has both bounds.