SCS Lab 04 - Cap a role with a permission boundary

Scaffold: 3/5. The demo bucket, the over-granted developer role, and the managed policy that sits as that role’s permission boundary are all built and wired. What is missing is the one thing that makes the boundary worth having: the boundary’s own policy document. You write it.

The scenario

A developer role was handed s3:* and iam:* on every resource. That is the access someone reaches for when they are in a hurry, and it is exactly the access you do not want a role to keep. The obvious fix is to rewrite the identity policy, but you cannot always do that cleanly: the policy is shared, or generated, or owned by a team that will not narrow it this quarter, and in the meantime the role is dangerous.

A permission boundary caps a role without touching its identity policy. It is a managed policy you attach as the role’s PermissionsBoundary, and it sets a ceiling: the role’s effective permissions become the intersection of what its identity policy allows and what the boundary allows. Leave s3:DeleteObject out of the boundary and the role cannot delete objects, no matter that its identity policy still says s3:*. The identity policy stays broad; the boundary makes the broad part unreachable.

The requirement

DemoDeveloperRole keeps its broad identity policy (s3:* and iam:* on *). Its boundary, DeveloperBoundary, must cap the effective permissions to a safe set: read and list the demo bucket, put objects into it, emit CloudWatch metrics, and write logs. Nothing else. Specifically, no iam:* and no s3:Delete*.

What’s provided

Deploying src/template.yaml as shipped succeeds, but the boundary caps nothing: a boundary that allows everything imposes no ceiling, so the role’s effective permissions equal its dangerous identity policy. That is the failure the lab starts from.

Your task

Open src/template.yaml and replace the placeholder PolicyDocument in DeveloperBoundary so it allows exactly:

Do not add iam:* and do not add s3:Delete*. A boundary is a ceiling, not a grant: anything you leave out is capped away even though the identity policy still lists it. Use !GetAtt DemoBucket.Arn for the bucket and !Sub '${DemoBucket.Arn}/*' for its objects.

If you want the mechanics first, read the walk-through post linked at the bottom.

Run it

# Defaults: stack scs-lab-04, region ap-southeast-2.
./scripts/deploy.sh          # deploys src/template.yaml
./scripts/test.sh            # simulates the role's effective permissions
./scripts/teardown.sh        # empties the bucket and deletes everything

The stack creates a named role and a named managed policy, so deploy.sh passes CAPABILITY_NAMED_IAM.

What success looks like

test.sh never assumes the role or touches an object. It calls the IAM policy simulator, which evaluates a principal’s identity policies and its permission boundary together, so it reports the role’s effective decision for each action. It asks three questions and, once the boundary is tightened, prints:

  ok: s3:GetObject is allowed (allowed)
  ok: s3:DeleteObject is denied (implicitDeny) - the boundary caps it
  ok: iam:CreateUser is denied (implicitDeny) - the boundary caps it
PASS: the boundary lets the role read the demo bucket but caps away
s3:DeleteObject and iam:CreateUser, even though the identity policy grants
s3:* and iam:*. Effective permissions are the intersection of the two.

With the unedited src/template.yaml, the boundary allows * on *, so s3:DeleteObject and iam:CreateUser both come back allowed and the test fails.

If it fails

Reveal the solution

Deploy the complete reference template without editing anything:

SRC=solution ./scripts/deploy.sh && ./scripts/test.sh

What you just learned

Next

The rest of the SCS lab track is listed in labs/README-scs.md.