ЁЯПл The SchoolтА║ЁЯкк IAMтА║ЁЯз╛ рдзрдбрд╛ 12 тАФ Audit рдЖрдгрд┐ incident response: рдиреЛрдВрджрд╡рд╣реА рдЖрдгрд┐ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯз╛ рдзрдбрд╛ 12 тАФ Audit рдЖрдгрд┐ incident response: рдиреЛрдВрджрд╡рд╣реА рдЖрдгрд┐ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 12 тАФ рд╢реЗрд╡рдЯрдЪрд╛ рдзрдбрд╛! ┬╖ рдорд╛рдЧреАрд▓: lesson-11-least-privilege


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рд╕рдВрдкреВрд░реНрдг рдХреЛрд░реНрд╕, рдЖрдгрд┐ рд╢реЗрд╡рдЯрдЪрд╛ рдЯрдкреНрдкрд╛: auditor рд╕рд╛рд░рдЦрд╛ credential report (iam/data/credential-report.csv) рд╡рд╛рдЪрдгреЗ, CloudTrail рдордзреНрдпреЗ рдЪреЛрд░рд▓реЗрд▓реА key рдУрд│рдЦрдгреЗ (iam/data/cloudtrail.json), рдЖрдгрд┐ рдлреБрдЯрд▓реЗрд▓реНрдпрд╛ key рдЪреНрдпрд╛ incident рдЪреНрдпрд╛ рдкрд╣рд┐рд▓реНрдпрд╛ рддрд╛рд╕рд╛рд╕рд╛рдареА runbook.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рджрд░ рд╢реБрдХреНрд░рд╡рд╛рд░реА рдУрд│рдЦрдкрддреНрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдиреЛрдВрджреА рд╡рд╛рдЪрддреЗ: рдХреЛрдгрд╛рдХрдбреЗ рдХрд╛рд░реНрдб рдЖрд╣реЗ, рдХреЛрдгрд╛рдХрдбреЗ рдЬрд╛рджрд╛ рдЪрд╛рд╡реА рдЖрд╣реЗ, рдкреНрд░рддреНрдпреЗрдХ рдЪрд╛рд╡реА рдХрд┐рддреА рдЬреБрдиреА рдЖрд╣реЗ, рдХреЛрдгреА рджреБрд╕рд░реЗ рдХреБрд▓реВрдк рдХрдзреАрдЪ рд▓рд╛рд╡рд▓реЗ рдирд╛рд╣реА. рдЬреБрдиреНрдпрд╛ рдЪрд╛рд╡реНрдпрд╛ рдЖрдгрд┐ рдирд╕рд▓реЗрд▓реА рдХреБрд▓рдкреЗ рджреБрд░реБрд╕реНрдд рдХрд░рдгреНрдпрд╛рдЪреНрдпрд╛ рдпрд╛рджреАрдд рдЬрд╛рддрд╛рдд.

рдПрдХрд╛ рд░рд╛рддреНрд░реА рдиреЛрдВрджрд╡рд╣реАрдд рдХрд╛рд╣реАрддрд░реА рд╡рд┐рдЪрд┐рддреНрд░ рджрд┐рд╕рддреЗ: рдХрддрд░рд┐рдирд╛рдЪреА рдЪрд╛рд╡реА, рдЬреА рдлрдХреНрдд рд╕рдХрд╛рд│реА рд╢рд╛рд│реЗрддрдЪ рд╡рд╛рдкрд░рд▓реА рдЬрд╛рддреЗ, рддреА рд░рд╛рддреНрд░реА 2 рд╡рд╛рдЬрддрд╛ рджреБрд╕рд▒реНрдпрд╛ рд╢рд╣рд░рд╛рддреВрди рд╡рд╛рдкрд░рд▓реА рдЧреЗрд▓реА, рдЖрдгрд┐ рдХрддрд░рд┐рдирд╛ рдХрдзреАрдЪ рдЬрд╛рдд рдирд╛рд╣реА рдЕрд╢рд╛ рдЦреЛрд▓реНрдпрд╛ рдЙрдШрдбрдгреНрдпрд╛рдЪрд╛ рдкреНрд░рдпрддреНрди рдЭрд╛рд▓рд╛. рджрд╛рд░рд╛рдВрдиреА рдирд╛рд╣реА рдореНрд╣рдЯрд▓реЗ тАФ рдкрдг рдкреБрдврдЪреЗ рджрд╛рд░ рдХрд╛рдп рдореНрд╣рдгрддреЗ рд╣реЗ рдкрд╛рд╣рдгреНрдпрд╛рд╕рд╛рдареА рдХрд╛рд░реНрдпрд╛рд▓рдп рдерд╛рдВрдмрдд рдирд╛рд╣реА. рддреЗ:

  1. рдЪрд╛рд╡реА рдмрдВрдж рдХрд░рддреЗ, 2. рддрд┐рдЪреНрдпрд╛рдиреЗ рдЖрдзреАрдЪ рджрд┐рд▓реЗрд▓реЗ рд╕рдЧрд│реЗ рдкрд╛рд╕ рд░рджреНрдж рдХрд░рддреЗ,
  2. рдЕрдиреЛрд│рдЦреА рд╡реНрдпрдХреНрддреАрдиреЗ рдХрд╢рд╛рд▓рд╛ рд╣рд╛рдд рд▓рд╛рд╡рд▓рд╛ рддреЗ рдкрд╛рд╣рдгреНрдпрд╛рд╕рд╛рдареА рдкреНрд░рддреНрдпреЗрдХ рдЦреЛрд▓реАрдд рдлрд┐рд░рддреЗ, 4. рдХрддрд░рд┐рдирд╛рд▓рд╛ рдирд╡реА рдЪрд╛рд╡реА рджреЗрддреЗ,
  3. рдЪрд╛рд╡реА рдмрд╛рд╣реЗрд░ рдХрд╢реА рдЧреЗрд▓реА рддреЗ рд╢реЛрдзреВрди рдХрд╛рдврддреЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    cr["ЁЯУЛ credential report<br/>katrina: no MFA ┬╖ key 329 days"]
    ct["ЁЯЪи CloudTrail<br/>AKIAEXAMPLEKATRINA01 ┬╖ 02:03 ┬╖ us-east-1 ┬╖ new IP<br/>GetCallerIdentity ┬╖ RunInstances ┬╖ CreateUser"]
    k1["1 deactivate the key"]
    k2["2 revoke sessions"]
    k3["3 find what it created"]
    k4["4 rotate"]
    k5["5 find how it leaked"]
    cr -.->|"weekly"| ct
    ct --> k1 --> k2 --> k3 --> k4 --> k5

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/iam/lesson-diagrams.html#l12

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдкреНрд░рддрд┐рдмрдВрдз рдХрдзреАрдЪ рдкрд░рд┐рдкреВрд░реНрдг рдирд╕рддреЛ. Credentials public repos рдордзреВрди рдХрд╛рд╣реА рдорд┐рдирд┐рдЯрд╛рдВрдд рдлреБрдЯрддрд╛рдд, рдЖрдгрд┐ attackers рдкреБрдврдЪреЗ рдХрд╛рдо automate рдХрд░рддрд╛рдд. рдШрд╛рдмрд░рд╡рдгрд╛рд░рд╛ рдкреНрд░рд╕рдВрдЧ рдЖрдгрд┐ рдЖрдкрддреНрддреА рдпрд╛рдВрдЪреНрдпрд╛рддрд▓рд╛ рдлрд░рдХ рдореНрд╣рдгрдЬреЗ рдХреЛрдгреА рдХрд┐рддреА рд▓рд╡рдХрд░ рдиреЛрдВрджрд╡рд╣реА рд╡рд╛рдЪрддреЗ рдЖрдгрд┐ рдкрд╣рд┐рд▓реНрдпрд╛ рддрд╛рд╕рд╛рдЪрд╛ рдХрд┐рддреА рд╕рд░рд╛рд╡ рдЭрд╛рд▓рд╛ рдЖрд╣реЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

iam/demo.py рдордзреАрд▓ audit() credential report рд╡рд╛рдЪрддреЗ (рдард░рд▓реЗрд▓реНрдпрд╛ рддрд╛рд░рдЦреЗрд▓рд╛, 2026-09-27, рдореНрд╣рдгрдЬреЗ рдЖрдХрдбреЗ рдХрдзреАрдЪ рдмрджрд▓рдд рдирд╛рд╣реАрдд) рдЖрдгрд┐ MFA рдирд╕рд▓реЗрд▓реЗ passwords, 90 рджрд┐рд╡рд╕рд╛рдВрдкреЗрдХреНрд╖рд╛ рдЬреБрдиреНрдпрд╛ keys рдЖрдгрд┐ 90 рджрд┐рд╡рд╕ рди рд╡рд╛рдкрд░рд▓реЗрд▓реНрдпрд╛ keys рд╡рд░ рдмреЛрдЯ рдареЗрд╡рддреЗ. рдордЧ рддреЗ CloudTrail events рд╡рд╛рдЪрддреЗ, рдЖрджрд▓реНрдпрд╛ рджрд┐рд╡рд╕рд╛рд╡рд░реВрди katrina рдЪреЗ рдиреЗрд╣рдореАрдЪреЗ IPs рд╢рд┐рдХрддреЗ, рдЖрдгрд┐ рдирд╡реНрдпрд╛ IP рдордзреВрди рдЖрд▓реЗрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ event рдЪрд┐рдиреНрд╣рд╛рдВрдХрд┐рдд рдХрд░рддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 iam/demo.py audit
python3 - <<'EOF'
import csv, json
from datetime import date
today = date(2026, 9, 27)
for r in csv.DictReader(open("iam/data/credential-report.csv")):
    if r["access_key_1_active"] == "true":
        print(r["user"].ljust(9), "key age", (today - date.fromisoformat(r["access_key_1_last_rotated"])).days, "days")
events = json.load(open("iam/data/cloudtrail.json"))
key = [e for e in events if e["accessKey"] == "AKIAEXAMPLEKATRINA01"]
print("AKIAEXAMPLEKATRINA01 used from:", sorted({e["sourceIPAddress"] for e in key}), "in", sorted({e["awsRegion"] for e in key}))
print("denied calls:", [e["eventName"] for e in key if e.get("errorCode")])
EOF
python3 iam/test_iam.py

рдЦрд▒реНрдпрд╛ account рд╡рд░ (AWS CLI рдЖрдгрд┐ credentials рд▓рд╛рдЧрддрд╛рдд) тАФ рдЖрдзреА audit, рдордЧ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕:

aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 --decode > credential-report.csv
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLEKATRINA01 --max-results 50
aws iam update-access-key --user-name katrina --access-key-id AKIAEXAMPLEKATRINA01 --status Inactive
cat > revoke-older-sessions.json <<'JSON'
{"Version": "2012-10-17", "Statement": [{"Effect": "Deny", "Action": "*", "Resource": "*",
  "Condition": {"DateLessThan": {"aws:TokenIssueTime": "2026-09-27T03:00:00Z"}}}]}
JSON
aws iam put-user-policy --user-name katrina --policy-name revoke-older-sessions --policy-document file://revoke-older-sessions.json

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

audit <root_account>, aishwarya рдЖрдгрд┐ ci-robot рд╕рд╛рдареА тЬЕ рдЫрд╛рдкрддреЗ; тЪая╕П katrina password without MFA, key 329 days old; тЪая╕П dipika password without MFA, key 931 days old, key unused > 90 days тЖТ delete; рдордЧ рд╕рд╣рд╛ CloudTrail рдУрд│реА, рдЬреНрдпрд╛рдд 198.51.100.77 рдордзреВрди рдЖрд▓реЗрд▓реНрдпрд╛ katrina рдЪреНрдпрд╛ рддреАрди events рд╡рд░ ЁЯЪи рдЕрд╕рддреЗ (GetCallerIdentity, sa-east-1 рдордзреНрдпреЗ RunInstances рдЖрдгрд┐ CreateUser, рд╢реЗрд╡рдЯрдЪреНрдпрд╛ рджреЛрдиреНрд╣реАрдВрд╡рд░ AccessDenied), рдЖрдгрд┐ рдкрд╛рдЪ рдкрд╛рдпрд▒реНрдпрд╛рдВрдЪрд╛ runbook. рддреБрдордЪрд╛ snippet key рдЪреЗ рд╡рдп 329, 931 рдЖрдгрд┐ 28 рджрд┐рд╡рд╕ (katrina, dipika, ci-robot), рддреАрди regions рдордзрд▓реЗ key рдЪреЗ рджреЛрди IPs, рдЖрдгрд┐ denied calls: ['RunInstances', 'CreateUser'] рдЫрд╛рдкрддреЛ. Tests рд╢реЗрд╡рдЯреА 12/12 passed рджрд╛рдЦрд╡рддрд╛рдд.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рддреБрдореНрд╣реА рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдиреЛрдВрджреА auditor рд╕рд╛рд░рдЦреНрдпрд╛ рд╡рд╛рдЪреВ рд╢рдХрддрд╛, рдкрд╣рд┐рд▓реНрдпрд╛ рдХрд╛рд╣реА calls рд╡рд░реВрдирдЪ рдЪреЛрд░рд▓реЗрд▓реА key рдУрд│рдЦреВ рд╢рдХрддрд╛, рдЖрдгрд┐ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕ рдпреЛрдЧреНрдп рдХреНрд░рдорд╛рдиреЗ рд╣рд╛рддрд╛рд│реВ рд╢рдХрддрд╛.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рд╕рд░рд╛рд╡ рдХреЗрд▓реЗрд▓рд╛, рд╡рд░реАрд▓ patterns рд╡рд░ GuardDuty alert рджреЗрдгрд╛рд░рд╛ рдлреБрдЯрд▓реЗрд▓реНрдпрд╛ key рдЪрд╛ runbook AWS рдЪрд╛рд▓рд╡рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ team рд╕рд╛рдареА рдиреЗрд╣рдореАрдЪрд╛ рдЖрд╣реЗ. рдкрд╛рдпрд░реА 4 рдЪреА рд╕рд░реНрд╡реЛрддреНрддрдо рдЖрд╡реГрддреНрддреА рдореНрд╣рдгрдЬреЗ "рдлреБрдЯрд╛рдпрд▓рд╛ рджреАрд░реНрдШрдХрд╛рд│рд╛рдЪреА key рдирд╡реНрд╣рддреАрдЪ" тАФ рдзрдбреЗ 05, 06 рдЖрдгрд┐ 10.

ЁЯОУ рдУрд│рдЦрдкрддреНрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп рдЖрддрд╛ рддреБрдордЪреЗ рдЖрд╣реЗ

рдУрд│рдЦрдкрддреНрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп тЖТ рдХрд╛рд░реНрдбреЗ рдЖрдгрд┐ рд╡рд░реНрдЧрд╛рдВрдЪреНрдпрд╛ рдпрд╛рджреНрдпрд╛ тЖТ рд▓рд┐рдЦрд┐рдд рдирд┐рдпрдо тЖТ deny рдЬрд┐рдВрдХрддреЛ тЖТ рдХрд╛рд░реНрдбреЗ рдирд╡реНрд╣реЗ, рдЯреЛрдкреНрдпрд╛ тЖТ secrets рдирд╕рд▓реЗрд▓реЗ badges тЖТ "рдлрдХреНрдд рддреЗрд╡реНрд╣рд╛рдЪтАж" тЖТ рдЫрддреЗ тЖТ рджреЛрди рджрд░рд╡рд╛рдЬреЗ, рджреЛрди рдЪрд╛рд╡реНрдпрд╛ тЖТ рдПрдХрдЪ front desk тЖТ рд╡рд╛рдкрд░рд▓реЗ рдЬрд╛рддреЗ рддреЗрд╡рдвреЗрдЪ рдареЗрд╡рд╛ тЖТ рдиреЛрдВрджрд╡рд╣реА рдЖрдгрд┐ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕. рддреБрдореНрд╣реА рдлрдХреНрдд IAM рд╢рд┐рдХрд▓рд╛ рдирд╛рд╣реАрдд тАФ рддреБрдореНрд╣реА рдУрд│рдЦрдкрддреНрд░ рдХрд╛рд░реНрдпрд╛рд▓рдп рдЪрд╛рд▓рд╡реВ рд╢рдХрддрд╛. ЁЯккЁЯОУ

рд╢рд╛рд│реЗрддреАрд▓ рдкреБрдврдЪреА рджрд╛рд░реЗ: AWS school рдпрд╛ policies рдЬреНрдпрд╛ services рдЪреЗ рд░рдХреНрд╖рдг рдХрд░рддрд╛рдд рддреНрдпрд╛ рдмрдирд╡рддреЗ; CI/CD school рдзрдбрд╛ 06 рдЪрд╛ OIDC role рдЦрд▒реНрдпрд╛ pipeline рдордзреНрдпреЗ рдХрд╛рдорд╛рд▓рд╛ рд▓рд╛рд╡рддреЗ; Kubernetes school рдордзреНрдпреЗ IRSA рдЖрдгрд┐ Pod Identity рд░рд╛рд╣рддрд╛рдд.

git checkout main
python3 iam/demo.py     # one last run, for fun

ЁЯз╛ Lesson 12 тАФ Audit & incident response: the logbook and the first hour

ЁЯУН You are here: Lesson 12 of 12 тАФ the final lesson! ┬╖ Previous: lesson-11-least-privilege


ЁЯУж What's in this branch

The complete course, plus the last mile: reading the credential report (iam/data/credential-report.csv) like an auditor, spotting a stolen key in CloudTrail (iam/data/cloudtrail.json), and the runbook for the first hour of a leaked-key incident.

ЁЯзТ Explain like I'm 5

Every Friday the ID office reads its own records: who has a card, who has a spare key, how old each key is, who never set up the second lock. Old keys and missing locks go on a list to fix.

One night the logbook shows something odd: Katrina's key, which is only ever used in the school in the morning, was used at 2 a.m. from another city, trying to open rooms Katrina never visits. The doors said no тАФ but the office does not wait to find out what the next door says. It:

  1. switches the key off, 2. cancels any passes already issued with it,
  2. walks every room to see what the stranger touched, 4. gives Katrina a new key,
  3. finds out how the key got out.

ЁЯЧ║я╕П Diagram

flowchart LR
    cr["ЁЯУЛ credential report<br/>katrina: no MFA ┬╖ key 329 days"]
    ct["ЁЯЪи CloudTrail<br/>AKIAEXAMPLEKATRINA01 ┬╖ 02:03 ┬╖ us-east-1 ┬╖ new IP<br/>GetCallerIdentity ┬╖ RunInstances ┬╖ CreateUser"]
    k1["1 deactivate the key"]
    k2["2 revoke sessions"]
    k3["3 find what it created"]
    k4["4 rotate"]
    k5["5 find how it leaked"]
    cr -.->|"weekly"| ct
    ct --> k1 --> k2 --> k3 --> k4 --> k5

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/iam/lesson-diagrams.html#l12

тЭУ What

ЁЯдФ Why

Because prevention is never perfect. Credentials leak through public repos within minutes, and attackers automate what they do next. The difference between a scare and a disaster is how fast someone reads the logbook and how rehearsed the first hour is.

ЁЯФз How (in this repo)

audit() in iam/demo.py reads the credential report (on a fixed date, 2026-09-27, so the numbers never drift) and flags passwords without MFA, keys older than 90 days and keys unused for 90 days. Then it reads the CloudTrail events, learns katrina's normal IPs from the day before, and marks every event from a new one.

ЁЯзк Try it

python3 iam/demo.py audit
python3 - <<'EOF'
import csv, json
from datetime import date
today = date(2026, 9, 27)
for r in csv.DictReader(open("iam/data/credential-report.csv")):
    if r["access_key_1_active"] == "true":
        print(r["user"].ljust(9), "key age", (today - date.fromisoformat(r["access_key_1_last_rotated"])).days, "days")
events = json.load(open("iam/data/cloudtrail.json"))
key = [e for e in events if e["accessKey"] == "AKIAEXAMPLEKATRINA01"]
print("AKIAEXAMPLEKATRINA01 used from:", sorted({e["sourceIPAddress"] for e in key}), "in", sorted({e["awsRegion"] for e in key}))
print("denied calls:", [e["eventName"] for e in key if e.get("errorCode")])
EOF
python3 iam/test_iam.py

On a real account (needs the AWS CLI and credentials) тАФ the audit, then the first hour:

aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 --decode > credential-report.csv
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLEKATRINA01 --max-results 50
aws iam update-access-key --user-name katrina --access-key-id AKIAEXAMPLEKATRINA01 --status Inactive
cat > revoke-older-sessions.json <<'JSON'
{"Version": "2012-10-17", "Statement": [{"Effect": "Deny", "Action": "*", "Resource": "*",
  "Condition": {"DateLessThan": {"aws:TokenIssueTime": "2026-09-27T03:00:00Z"}}}]}
JSON
aws iam put-user-policy --user-name katrina --policy-name revoke-older-sessions --policy-document file://revoke-older-sessions.json

тЬЕ Verify тАФ what you should see

audit prints тЬЕ for <root_account>, aishwarya and ci-robot; тЪая╕П katrina password without MFA, key 329 days old; тЪая╕П dipika password without MFA, key 931 days old, key unused > 90 days тЖТ delete; then six CloudTrail lines with ЁЯЪи on katrina's three events from 198.51.100.77 (GetCallerIdentity, RunInstances in sa-east-1 and CreateUser, the last two with AccessDenied), and the five-step runbook. Your snippet prints key ages 329, 931 and 28 days (katrina, dipika, ci-robot), the key's two IPs across three regions, and denied calls: ['RunInstances', 'CreateUser']. The tests end with 12/12 passed.

ЁЯПБ What you just proved

You can read the office's own records like an auditor, recognise a stolen key from its first few calls, and run the first hour in the right order.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: a leaked-key runbook, rehearsed, with GuardDuty alerting on the patterns above, is standard for any team that runs AWS. The best version of step 4 is "there was no long-lived key to leak" тАФ lessons 05, 06 and 10.

ЁЯОУ The ID office is yours

The ID office тЖТ cards and class lists тЖТ the written rules тЖТ deny wins тЖТ hats, not cards тЖТ badges without secrets тЖТ "only whenтАж" тЖТ ceilings тЖТ two doors, two keys тЖТ one front desk тЖТ trim to what is used тЖТ the logbook and the first hour. You didn't just learn IAM тАФ you can run the ID office. ЁЯккЁЯОУ

Next doors in the school: the AWS school builds the services these policies protect; the CI/CD school puts lesson 06's OIDC role to work in a real pipeline; the Kubernetes school is where IRSA and Pod Identity live.

git checkout main
python3 iam/demo.py     # one last run, for fun
тЖР Previousleast privilegeFinished! Take the quiz тЖТcheck what stuck

This page is the lesson's README from the lesson-12-audit-incidents branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.