Who decides which door opens?
An IAM policy is the written list of what a person or a program may do in a cloud account. Reading one correctly is how you stop a leak before it happens, and how you fix an "access denied" error.
A student card works the same way. It opens the library door but not the staff room, because somebody wrote down which doors each card may open.
By the end of this lesson
Note
I can read a policy and say whether a call is allowed.
Key words
| Word | What it means | Example |
|---|---|---|
| Policy | a document that lists what may and may not be done | |
| Statement | one rule inside a policy | |
| Effect | whether a statement allows or denies | |
| Action | the call being made | s3:GetObject |
| Resource | the thing the call is made on | |
| Least privilege | giving only the access a job needs |
The cells students fill in are left blank in this preview.
A policy is a list of statements
A policy is a JSON document. It has a Version and a list called Statement. Each statement says three things: the effect, the action and the resource.
The effect is Allow or Deny. The action is the call, such as s3:GetObject, which reads one file. The resource is what the call is made on, named by a long text called an ARN. These policies are invented. Nothing here needs an account.
Try it: read a policy
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::photos-demo/*",
},
{
"Effect": "Deny",
"Action": "s3:*",
"Resource": "arn:aws:s3:::photos-demo/private/*",
},
],
}
for statement in policy["Statement"]:
print(statement["Effect"], statement["Action"], statement["Resource"])
# Change "Deny" to "Allow". The program still reads it. Does the meaning change?
A star matches any run of characters
Writing every file name would take for ever. So a pattern may hold a star. A star matches any run of characters, including none at all.
The resource "arn:aws:s3:::photos-demo/" matches every object in that bucket. The action "s3:" matches every S3 call. A pattern with no star must match the text exactly.
Worked example: which texts a pattern matches
| Pattern | Text | Matches? |
|---|---|---|
| s3:* | s3:GetObject | Yes |
| s3:* | sqs:SendMessage | No, it does not start with s3: |
| s3:Get* | s3:GetObject | Yes |
| s3:Get* | s3:PutObject | No |
| photos/* | photos/ | Yes, the star matches nothing |
| *.jpg | cat.jpg | Yes |
| s3:GetObject | s3:GetObjectAcl | No, no star, so exact |
The cells students fill in are left blank in this preview.
A deny always wins
To decide one call, find every statement whose action and resource both match it. Then apply three rules.
If any matching statement says Deny, the call is refused. This is an explicit deny, and it wins over every Allow. If none denies but one allows, the call is allowed. If nothing matches, the call is refused anyway. That is an implicit deny.
Worked example: three calls on the photos policy
| Call | Statements that match | Result |
|---|---|---|
| s3:GetObject on photos-demo/cat.jpg | Allow only | Allowed |
| s3:GetObject on photos-demo/private/id.jpg | Allow and Deny | Refused: explicit deny wins |
| sqs:SendMessage on a queue | None | Refused: implicit deny |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people think a call is allowed unless a policy forbids it. It is the other way round. A call is refused unless a statement allows it, and a Deny beats any Allow.
Try it: three rules in code
matched = ["Allow", "Deny"]
if "Deny" in matched:
result = "refused: explicit deny"
elif "Allow" in matched:
result = "allowed"
else:
result = "refused: implicit deny"
print(result)
# Change matched to ["Allow"], then to []. What does each print?
Give the least that works
A photo site needs to read and add photos. It does not need to delete them or touch the queues. So its policy names only those two actions, on only that bucket.
This is least privilege. If the site is ever misused, the damage stops at what the policy allowed.