🏫 The School›🪪 IAM›✂️ धडा 11 — Least privilege: वापरले जाते तेवढेच ठेवा
🖼️ See the drawing + lab 🏠 Course home 🌿 Branch on GitHub ✏️ View source
🖼️ आकृती आणि labThe drawing + lab पूर्ण पानावर उघडा ↗Open full page ↗

✂️ धडा 11 — Least privilege: वापरले जाते तेवढेच ठेवा

📍 तुम्ही इथे आहात: 12 पैकी धडा 11 · मागील: lesson-10-people-at-scale · पुढील: lesson-12-audit-incidents


📦 या ब्रँचमध्ये काय आहे

धडे 01–10, आणि पुराव्यासह permissions कमी करणारे चक्र: काय दिले होते (teachers-wide.json) आणि 90 दिवसांत प्रत्यक्ष काय वापरले (iam/data/last-accessed.json) यांची तुलना करा, अरुंद policy तयार करा, आणि खरे काम अजूनही चालते हे सिद्ध करा.

🧒 5 वर्षांच्या मुलाला समजावल्यासारखे

शिक्षिकांचे कार्ड बनवताना कोणीतरी लिहिले "प्रत्येक खोली वापरू शकतात: ग्रंथालय, lab, स्वयंपाकघर, boiler room" — ते जलद होते. कार्यालय एक अभ्यागत नोंदवही सुद्धा ठेवते. तीन महिन्यांनंतर ती दाखवते की शिक्षिकांनी ग्रंथालय वापरले आणि lab मध्ये डोकावल्या. Boiler room मध्ये कधीच कोणी गेले नाही.

म्हणून कार्यालय नोंदवहीत दिसते तेवढेच ठेवून कार्ड कापते ✂️, मग दुसऱ्या दिवशी सकाळी तपासते की प्रत्येक शिक्षिका अजूनही तिचे खरे काम करू शकते. आता boiler room त्यांच्यासाठी बंद आहे — आणि एखाद्या शिक्षिकेचे कार्ड कधी चोरीला गेले तरी चोरही तिथे पोहोचू शकत नाही.

🗺️ आकृती

flowchart LR
    wide["📝 granted<br/>s3:* · ec2:* · dynamodb:*"]
    log["📊 last accessed · 90 days<br/>s3:GetObject · s3:ListBucket<br/>s3:PutObject · ec2:DescribeInstances"]
    gen["✂️ generated policy<br/>4 actions"]
    narrow["🎯 + real Resource ARNs<br/>school-reports only"]
    test["✅ used actions still ALLOW<br/>🚫 dynamodb:DeleteTable"]
    wide --> log -->|"1 compare"| gen -->|"2 narrow resources"| narrow -->|"3 prove"| test

🗺️ काढलेली आकृती + एक lab: https://school-edh.pages.dev/iam/lesson-diagrams.html#l11

❓ काय

🤔 का

कारण प्रत्येक न वापरलेली permission म्हणजे फक्त धोका: ती कोणालाच मदत करत नाही आणि credentials मिळवणाऱ्या प्रत्येक attacker ला मदत करते. आणि काय काढायचे याचा अंदाज बांधला की गोष्टी बिघडतात — म्हणूनच teams कमी करणे थांबवतात. पुराव्यामुळे कमी करणे सुरक्षित होते.

🔧 कसे (या repo मध्ये)

iam/demo.py मधील leastpriv() last-accessed.json वाचते, दिलेल्या पण कधीच न वापरलेल्या services छापते, actions_used वरून अरुंद policy बनवते, आणि decide() ला एक वापरलेली आणि एक न वापरलेली action विचारते.

🧪 करून पाहा

python3 iam/demo.py leastpriv
python3 - <<'EOF'
import json, sys; sys.path.insert(0, "iam"); from evaluate import decide
used = json.load(open("iam/data/last-accessed.json"))["actions_used"]
narrow = {"Version": "2012-10-17", "Statement": [
    {"Sid": "ReportsOnly", "Effect": "Allow", "Action": ["s3:GetObject", "s3:ListBucket", "s3:PutObject"],
     "Resource": ["arn:aws:s3:::school-reports", "arn:aws:s3:::school-reports/*"]},
    {"Sid": "SeeInstances", "Effect": "Allow", "Action": "ec2:DescribeInstances", "Resource": "*"}]}
teachers = "arn:aws:iam::111122223333:role/teachers"
def res(a): return "*" if a.startswith("ec2") else ("arn:aws:s3:::school-reports" if a == "s3:ListBucket" else "arn:aws:s3:::school-reports/term1.pdf")
print("everything used still works:", all(decide(teachers, a, res(a), identity=[("narrow", narrow)])[0] == "ALLOW" for a in used))
for a, r in [("s3:GetObject", "arn:aws:s3:::payroll/salaries.csv"), ("ec2:TerminateInstances", "*"), ("dynamodb:DeleteTable", "*")]:
    print(a.ljust(24), r.ljust(36), decide(teachers, a, r, identity=[("narrow", narrow)])[0])
EOF

खऱ्या account वर (AWS CLI आणि credentials लागतात; narrow.json म्हणजे file म्हणून save केलेली अरुंद policy):

aws iam generate-service-last-accessed-details --arn arn:aws:iam::111122223333:role/teachers --granularity ACTION_LEVEL
aws iam get-service-last-accessed-details --job-id <JobId from the previous command>
aws accessanalyzer check-no-new-access --policy-type IDENTITY_POLICY --existing-policy-document file://iam/policies/teachers-wide.json --new-policy-document file://narrow.json

✅ तपासा — तुम्हाला काय दिसायला हवे

leastpriv दिलेले ['s3:*', 'ec2:*', 'dynamodb:*'] आणि 90 दिवसांत वापरलेल्या चार actions समोरासमोर छापते, 💤 services granted but never used: ['dynamodb:*'], तयार केलेली actions ची यादी, मग ✅ narrowed policy → s3:GetObject ALLOW आणि 🚫 narrowed policy → dynamodb:DeleteTable IMPLICIT DENY. तुमचा snippet छापतो everything used still works: True आणि payroll/salaries.csv वाचण्यासाठी, ec2:TerminateInstances आणि dynamodb:DeleteTable साठी IMPLICIT DENY. खऱ्या account वर check-no-new-access "result": "PASS" परत देते — जुन्या policy ने न दिलेले काहीही नवी policy देत नाही.

🏁 तुम्ही आत्ताच काय सिद्ध केले

तुम्ही एक रुंद policy प्रत्यक्ष वापरल्या जाणाऱ्यापुरती कापू शकता, तिचे resources अरुंद करू शकता, आणि ship करण्याआधी सिद्ध करू शकता — की खरे काम अजूनही चालते आणि बाकी सगळे बंद आहे.

⚠️ नेहमीच्या चुका

🏭 प्रत्यक्ष वापरात हे का महत्त्वाचे: least privilege हे प्रत्येक framework मधील (SOC 2, ISO 27001, PCI DSS) नाव असलेले control आहे. Auditors नेमका हाच पुरावा स्वीकारतात: last-accessed data, तयार केलेल्या policies, आणि नवा access रोखणारा CI check.

⏭️ पुढे

एक ना एक दिवस काहीतरी नक्की चुकेल. Audit आणि incident response — नोंदवही वाचणे, आणि पहिल्या तासात काय करायचे.

git checkout lesson-12-audit-incidents

✂️ Lesson 11 — Least privilege: trim to what is used

📍 You are here: Lesson 11 of 12 · Previous: lesson-10-people-at-scale · Next: lesson-12-audit-incidents


📦 What's in this branch

Lessons 01–10, plus the loop that shrinks permissions with evidence: compare what was granted (teachers-wide.json) with what was actually used in 90 days (iam/data/last-accessed.json), generate a narrower policy, and prove the real work still passes.

🧒 Explain like I'm 5

When the teachers' card was made, someone wrote "may use every room: library, lab, kitchen, boiler room" — it was quicker. The office also keeps a visitor log. After three months, it shows the teachers used the library and looked into the lab. Nobody ever went to the boiler room.

So the office cuts ✂️ the card down to what the log shows, then checks the next morning that every teacher can still do their real work. The boiler room is now closed to them — and if a teacher's card is ever stolen, the thief cannot get there either.

🗺️ Diagram

flowchart LR
    wide["📝 granted<br/>s3:* · ec2:* · dynamodb:*"]
    log["📊 last accessed · 90 days<br/>s3:GetObject · s3:ListBucket<br/>s3:PutObject · ec2:DescribeInstances"]
    gen["✂️ generated policy<br/>4 actions"]
    narrow["🎯 + real Resource ARNs<br/>school-reports only"]
    test["✅ used actions still ALLOW<br/>🚫 dynamodb:DeleteTable"]
    wide --> log -->|"1 compare"| gen -->|"2 narrow resources"| narrow -->|"3 prove"| test

🗺️ Drawn version + a lab: https://school-edh.pages.dev/iam/lesson-diagrams.html#l11

❓ What

🤔 Why

Because every unused permission is pure risk: it helps no one and helps every attacker who gets the credentials. And guessing what to remove breaks things — which is why teams stop trimming. Evidence makes trimming safe.

🔧 How (in this repo)

leastpriv() in iam/demo.py reads last-accessed.json, prints the services granted but never used, builds a narrowed policy from actions_used, and asks decide() about a used and an unused action.

🧪 Try it

python3 iam/demo.py leastpriv
python3 - <<'EOF'
import json, sys; sys.path.insert(0, "iam"); from evaluate import decide
used = json.load(open("iam/data/last-accessed.json"))["actions_used"]
narrow = {"Version": "2012-10-17", "Statement": [
    {"Sid": "ReportsOnly", "Effect": "Allow", "Action": ["s3:GetObject", "s3:ListBucket", "s3:PutObject"],
     "Resource": ["arn:aws:s3:::school-reports", "arn:aws:s3:::school-reports/*"]},
    {"Sid": "SeeInstances", "Effect": "Allow", "Action": "ec2:DescribeInstances", "Resource": "*"}]}
teachers = "arn:aws:iam::111122223333:role/teachers"
def res(a): return "*" if a.startswith("ec2") else ("arn:aws:s3:::school-reports" if a == "s3:ListBucket" else "arn:aws:s3:::school-reports/term1.pdf")
print("everything used still works:", all(decide(teachers, a, res(a), identity=[("narrow", narrow)])[0] == "ALLOW" for a in used))
for a, r in [("s3:GetObject", "arn:aws:s3:::payroll/salaries.csv"), ("ec2:TerminateInstances", "*"), ("dynamodb:DeleteTable", "*")]:
    print(a.ljust(24), r.ljust(36), decide(teachers, a, r, identity=[("narrow", narrow)])[0])
EOF

On a real account (needs the AWS CLI and credentials; narrow.json is the narrowed policy saved as a file):

aws iam generate-service-last-accessed-details --arn arn:aws:iam::111122223333:role/teachers --granularity ACTION_LEVEL
aws iam get-service-last-accessed-details --job-id <JobId from the previous command>
aws accessanalyzer check-no-new-access --policy-type IDENTITY_POLICY --existing-policy-document file://iam/policies/teachers-wide.json --new-policy-document file://narrow.json

✅ Verify — what you should see

leastpriv prints the granted ['s3:*', 'ec2:*', 'dynamodb:*'] against the four actions used in 90 days, 💤 services granted but never used: ['dynamodb:*'], the generated action list, then ✅ narrowed policy → s3:GetObject ALLOW and 🚫 narrowed policy → dynamodb:DeleteTable IMPLICIT DENY. Your snippet prints everything used still works: True and IMPLICIT DENY for reading payroll/salaries.csv, ec2:TerminateInstances and dynamodb:DeleteTable. On a real account, check-no-new-access returns "result": "PASS" — the new policy grants nothing the old one did not.

🏁 What you just proved

You can cut a wide policy down to what is actually used, narrow its resources, and prove — before shipping — that the real work still works and the rest is closed.

⚠️ Common mistakes

🏭 Why this matters in production: least privilege is a named control in every framework (SOC 2, ISO 27001, PCI DSS). The evidence auditors accept is exactly this: last-accessed data, generated policies, and a CI check that blocks new access.

⏭️ Next

Something will go wrong one day. Audit and incident response — reading the logbook, and what to do in the first hour.

git checkout lesson-12-audit-incidents
← Previouspeople at scaleNext →audit incidents

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