ЁЯПл The SchoolтА║ЁЯЦея╕П EC2тА║ЁЯкк рдзрдбрд╛ 07 тАФ Metadata рдЖрдгрд┐ roles: "рдореА рдХреЛрдг?" рдЖрдгрд┐ badge
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯкк рдзрдбрд╛ 07 тАФ Metadata рдЖрдгрд┐ roles: "рдореА рдХреЛрдг?" рдЖрдгрд┐ badge

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 07 ┬╖ рдорд╛рдЧреЗ: lesson-06-connecting ┬╖ рдкреБрдвреЗ: lesson-08-pricing


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

рдзрдбреЗ 01тАУ06, рдЖрдгрд┐ instance metadata service (рдкреНрд░рддреНрдпреЗрдХ рдбреЗрд╕реНрдХ "рдореА рдХреЛрдг?" рд╡рд┐рдЪрд╛рд░реВ рд╢рдХрддреЛ), IMDSv2 рдЖрдгрд┐ hop limit 1 рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗрдд, рдЖрдгрд┐ instance profile тАФ рдбреЗрд╕реНрдХрд▓рд╛ рдЕрд▓реНрдкрдХрд╛рд│ рдЪрд╛рд▓рдгрд╛рд░реА credentials рджреЗрдгрд╛рд░рд╛ badge, рдореНрд╣рдгрдЬреЗ рддреНрдпрд╛рд╡рд░ рдХреЛрдгрд╛рд▓рд╛рд╣реА keys рдЪрд┐рдХрдЯрд╡рд╛рд╡реНрдпрд╛ рд▓рд╛рдЧрдд рдирд╛рд╣реАрдд. Lab рдореНрд╣рдгрдЬреЗ ec2/check_template.py, рдПрдХ reviewer рдЬреЛ launch template рд╡рд╛рдЪрддреЛ рдЖрдгрд┐ рдХреЛрдгрддрд╛рд╣реА рдбреЗрд╕реНрдХ рдмрдирдгреНрдпрд╛рдЖрдзреА рдХрд╛рдп рджреБрд░реБрд╕реНрдд рдХрд░рд╛рдпрдЪреЗ рддреНрдпрд╛рдЪреА рдпрд╛рджреА рджреЗрддреЛ тАФ ec2/templates/risky.json рдЖрдгрд┐ ec2/templates/web.json рд╡рд░ рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рд┐рд▓реЗрд▓рд╛.

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

рдкреНрд░рддреНрдпреЗрдХ рдбреЗрд╕реНрдХрдЪреНрдпрд╛ рднрд┐рдВрддреАрд╡рд░ рдПрдХ рдЫреЛрдЯреЗ рдорджрдд-рдХреЗрдВрджреНрд░ ЁЯЫОя╕П рдЕрд╕рддреЗ, рдЬрд┐рдереЗ рдлрдХреНрдд рддреЛрдЪ рдбреЗрд╕реНрдХ рдкреЛрд╣реЛрдЪреВ рд╢рдХрддреЛ. рддреНрдпрд╛рд▓рд╛ рд╡рд┐рдЪрд╛рд░рд╛ "рдореА рдХреЛрдгрддрд╛ рдбреЗрд╕реНрдХ? рдХреЛрдгрддреА рдЗрдорд╛рд░рдд? рдорд╛рдЭреА рдкрд╣рд┐рд▓реНрдпрд╛ boot рдЪреА script рдХреЛрдгрддреА рд╣реЛрддреА?" рдЖрдгрд┐ рддреЗ рдЙрддреНрддрд░ рджреЗрддреЗ. рдбреЗрд╕реНрдХрдиреЗ badge ЁЯкк рдШрд╛рддрд▓рд╛ рдЕрд╕реЗрд▓ (role рдЕрд╕рд▓реЗрд▓реЗ рдПрдХ instance profile), рддрд░ рдорджрдд-рдХреЗрдВрджреНрд░ рдбреЗрд╕реНрдХрд▓рд╛ рджрд┐рд╡рд╕рд╛рдЪреЗ рдкрд╛рд╕ рд╕реБрджреНрдзрд╛ рджреЗрдК рд╢рдХрддреЗ тАФ рдХрд╛рд╣реА рддрд╛рд╕рд╛рдВрдиреА рд╕рдВрдкрдгрд╛рд▒реНрдпрд╛ рдЖрдгрд┐ рдЖрдкреЛрдЖрдк рдирд╡реНрдпрд╛ рд╣реЛрдгрд╛рд▒реНрдпрд╛ рдкрд░рд╡рд╛рдирдЧреНрдпрд╛. рдбреЗрд╕реНрдХрдЦрд╛рд▓реА рдЪрд┐рдХрдЯрд╡рд▓реЗрд▓реНрдпрд╛ keys рдирд╛рд╣реАрдд.

рдЬреБрдиреЗ рдорджрдд-рдХреЗрдВрджреНрд░ (IMDSv1) рддреНрдпрд╛рд▓рд╛ рдЪрд┐рдареНрдареА рдкрд╛рдард╡рдгрд╛рд▒реНрдпрд╛ рдХреЛрдгрд╛рд▓рд╛рд╣реА рдЙрддреНрддрд░ рджреНрдпрд╛рдпрдЪреЗ. рддреНрдпрд╛рдореБрд│реЗ рдПрдЦрд╛рджреА рд▓рдмрд╛рдб рд╡реНрдпрдХреНрддреА рдбреЗрд╕реНрдХрд╡рд░рдЪреНрдпрд╛ рдорджрддреАрд▓рд╛ рддрдпрд╛рд░ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ program рдХрдбреЗ рдЪрд┐рдареНрдареА рдкрд╛рдард╡реВ рд╢рдХрд╛рдпрдЪреА тАФ "рдХреГрдкрдпрд╛ рдорджрдд-рдХреЗрдВрджреНрд░рд╛рдХрдбреВрди рджрд┐рд╡рд╕рд╛рдЪрд╛ рдкрд╛рд╕ рдорд╛рдЧ рдЖрдгрд┐ рдЙрддреНрддрд░ рдорд▓рд╛ рд╡рд╛рдЪреВрди рджрд╛рдЦрд╡" тАФ рдЖрдгрд┐ рддреЛ рдШреЗрдКрди рдкрд╕рд╛рд░ рд╡реНрд╣рд╛рдпрдЪреА.

рдирд╡реЗ рдорджрдд-рдХреЗрдВрджреНрд░ (IMDSv2) рдЖрдзреА рдореНрд╣рдгрддреЗ: "рд╕реНрд╡рддрдГ рдЗрдереЗ рдпреЗ рдЖрдгрд┐ session ticket (рдПрдХ token) рд╕рд╛рдареА sign in рдХрд░; рдордЧ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрдирд╛рд╕реЛрдмрдд рддреЗ ticket рджрд╛рдЦрд╡." рдорджрддрдирд┐рд╕рд╛рдорд╛рд░реНрдлрдд рдкрд╛рдард╡рд▓реЗрд▓реА рдЪрд┐рдареНрдареА рд╕рд╣рд╕рд╛ рддреА sign-in рдЪреА рдкрд╛рдпрд░реА рдХрд░реВ рд╢рдХрдд рдирд╛рд╣реА. рдЖрдгрд┐ ticket рдлрдХреНрдд рдПрдХрдЪ рдкрд╛рдКрд▓ рдЬрд╛рддреЗ (hop limit): рддреБрдореНрд╣реА рдкрд░рд╡рд╛рдирдЧреА рджрд┐рд▓реЗрд▓реНрдпрд╛рдкреЗрдХреНрд╖рд╛ рддреЗ рдбреЗрд╕реНрдХрдкрд╛рд╕реВрди рджреВрд░ рдиреЗрддрд╛ рдпреЗрдд рдирд╛рд╣реА.

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

flowchart LR
    app["ЁЯЦея╕П program on the desk"]
    sess["1 sign in: ask for a session token<br/>(IMDSv2: HttpTokens = required)"]
    imds["ЁЯЫОя╕П metadata service<br/>link-local, only this desk"]
    q["2 every question carries the token"]
    hop["hop limit 1: the token cannot travel<br/>past the desk (2 only for containers that need it)"]
    badge["ЁЯкк instance profile тЖТ role<br/>short-lived credentials, rotated automatically"]
    app --> sess --> imds
    app --> q --> imds
    imds -.-> hop
    badge -->|"3 SDKs pick these up тАФ you write no code"| app
    check["ЁЯФН check_template.py reads the recipe card first<br/>risky.json: 6 findings ┬╖ web.json: nothing risky"]
    check -.->|"0 before any desk exists"| sess

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдЪреЛрд░рд▓реЗрд▓реНрдпрд╛ рджреАрд░реНрдШрдХрд╛рд│ рдЪрд╛рд▓рдгрд╛рд▒реНрдпрд╛ access keys рд╣рд╛ AWS accounts рдлреЛрдбрдгреНрдпрд╛рдЪрд╛ рд╕рд░реНрд╡рд╛рдд рдиреЗрд╣рдореАрдЪрд╛ рдПрдХ рдорд╛рд░реНрдЧ рдЖрд╣реЗ, рдЖрдгрд┐ IMDSv1 рд╡рд┐рд░реБрджреНрдзрдЪреНрдпрд╛ SSRF рдиреЗ рдЦрд▒реНрдпрд╛ incidents рдордзреНрдпреЗ "рдПрдХ web bug" рдЪреЗ рд░реВрдкрд╛рдВрддрд░ "attacker рдХрдбреЗ server рдЪрд╛ role рдЖрд╣реЗ" рдордзреНрдпреЗ рдХреЗрд▓реЗ рдЖрд╣реЗ. IMDSv2, hop limit 1 рдЖрдгрд┐ рдорд░реНрдпрд╛рджрд┐рдд role рд╣реА рд╕рд╛рдЦрд│реА рддреАрди рдард┐рдХрд╛рдгреА рддреЛрдбрддрд╛рдд тАФ launch template рдордзрд▓реНрдпрд╛ рддреАрди рдУрд│реАрдВрдЪреНрдпрд╛ рдХрд┐рдорддреАрдд.

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

ec2/check_template.py рдордзреНрдпреЗ рд╕рд╣рд╛ рдирд┐рдпрдо рдЖрд╣реЗрдд тАФ IMDSv2 required рдирд╛рд╣реА, hop limit 2 рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд, encrypted рдирд╕рд▓реЗрд▓рд╛ volume, instance profile рдирд╛рд╣реА, key pair (Session Manager рд▓рд╛ рдкреНрд░рд╛рдзрд╛рдиреНрдп рджреНрдпрд╛, рдзрдбрд╛ 06), рдЖрдгрд┐ gp3 рдРрд╡рдЬреА gp2 тАФ рдЖрдгрд┐ review() рдкреНрд░рддреНрдпреЗрдХ finding рддреНрдпрд╛рдЪреНрдпрд╛ рдЙрдкрд╛рдпрд╛рд╕рд╣ рдкрд░рдд рджреЗрддреЗ. ec2/demo.py рдордзрд▓реЗ metadata() рджреЛрдиреНрд╣реА templates review рдХрд░рддреЗ. рддреЗ рдлрдХреНрдд JSON рд╡рд╛рдЪрддреЗ; рддреЗ AWS рдХрд┐рдВрд╡рд╛ metadata service рд╢реА рдХрдзреАрдЪ рд╕рдВрдкрд░реНрдХ рдХрд░рдд рдирд╛рд╣реА. ec2/test_ec2.py рдордзрд▓рд╛ test L07 risky.json рд╕рд╛рдареА рдиреЗрдордХреНрдпрд╛ 6 findings рдЕрдкреЗрдХреНрд╖рд┐рдд рдзрд░рддреЛ.

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

python3 ec2/demo.py metadata
python3 ec2/check_template.py ec2/templates/risky.json
python3 ec2/check_template.py ec2/templates/web.json
python3 - <<'EOF'
import json, sys; sys.path.insert(0, "ec2")
from check_template import review
t = json.load(open("ec2/templates/risky.json"))
print("before:", len(review(t)), "finding(s)")
# fix the two metadata settings and give the desk a badge тАФ in memory; the file is untouched
t["MetadataOptions"] = {"HttpTokens": "required", "HttpPutResponseHopLimit": 1}
t["IamInstanceProfile"] = {"Name": "school-web"}
left = review(t)
print("after:", len(left), "finding(s):", [name for name, fix in left])
EOF

рдЖрддрд╛ рддреБрдордЪреА рдкрд╛рд│реА: рддреНрдпрд╛рдЪ snippet рдордзреНрдпреЗ рдЙрд░рд▓реЗрд▓реНрдпрд╛ рддреАрди рджреБрд░реБрд╕реНрдд рдХрд░рд╛ (volume encrypt рдХрд░рд╛, KeyName рдХрд╛рдвреВрди рдЯрд╛рдХрд╛, gp3 рд╡рд░ рдЬрд╛), рдЬреЛрдкрд░реНрдпрдВрдд рддреЛ after: 0 finding(s) рдЫрд╛рдкрдд рдирд╛рд╣реА.

рдЦрд▒реНрдпрд╛ account рд╡рд░ (AWS credentials рд▓рд╛рдЧрддрд╛рдд; рд╣реЗ settings рдмрджрд▓рддрд╛рдд, credentials рдирд╛рд╣реА тАФ рддреБрдордЪреЗ рд╕реНрд╡рддрдГрдЪреЗ IDs рдЖрдгрд┐ role рдирд╛рд╡ рд╡рд╛рдкрд░рд╛):

aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 \
    --http-tokens required --http-put-response-hop-limit 1 --http-endpoint enabled
aws ec2 describe-instances --instance-ids i-0123456789abcdef0 \
    --query 'Reservations[].Instances[].MetadataOptions'
aws iam create-instance-profile --instance-profile-name school-web
aws iam add-role-to-instance-profile --instance-profile-name school-web --role-name school-web-role
aws ec2 associate-iam-instance-profile --instance-id i-0123456789abcdef0 --iam-instance-profile Name=school-web
aws ec2 modify-instance-metadata-defaults --http-tokens required --http-put-response-hop-limit 2

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

metadata рд╕рд╣рд╛ тЪая╕П рдУрд│реАрдВрд╕рд╣ ЁЯУД risky.json: 6 finding(s) рдЫрд╛рдкрддреЗ тАФ metadata service v2 not required, metadata hop limit above 2, root volume not encrypted, no instance profile (the desk has no badge), a key pair for SSH, gp2 instead of gp3 тАФ рдордЧ ЁЯУД web.json: тЬЕ nothing risky. risky.json рд╡рд░ check_template.py 6 thing(s) to fix before launching рдиреЗ рд╕рдВрдкрддреЗ; web.json рд╡рд░ рддреЗ тЬЕ nothing risky found рдЫрд╛рдкрддреЗ. рддреБрдордЪрд╛ snippet рдЫрд╛рдкрддреЛ before: 6 finding(s) рдЖрдгрд┐ after: 3 finding(s): ['root volume not encrypted', 'a key pair for SSH', 'gp2 instead of gp3'].

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

рддреБрдореНрд╣реА launch template рд╡рд╛рдЪреВрди рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛ рдХреА рддреНрдпрд╛рдЪреЗ рдбреЗрд╕реНрдХ session-token metadata required рдареЗрд╡рддреАрд▓ рдХрд╛, token рдбреЗрд╕реНрдХрд╡рд░рдЪ рдареЗрд╡рддреАрд▓ рдХрд╛, рдЖрдгрд┐ keys рдРрд╡рдЬреА badge рдШрд╛рд▓рддреАрд▓ рдХрд╛ тАФ рдПрдХрд╣реА рдбреЗрд╕реНрдХ рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдпреЗрдгреНрдпрд╛рдЖрдзреА.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: "IMDSv2 required" рд╣рд╛ AWS Security Hub рдордзрд▓рд╛ рдЖрдгрд┐ рдмрд╣реБрддреЗрдХ audits рдордзрд▓рд╛ default check рдЖрд╣реЗ, рдЖрдгрд┐ рд╕рдВрд╕реНрдерд╛ ec2:MetadataHttpTokens рд╡рд░ SCPs рдиреЗ рддреЛ рд▓рд╛рдЧреВ рдХрд░рддрд╛рдд. рдорд░реНрдпрд╛рджрд┐рдд roles рд╕реЛрдмрдд, рдореНрд╣рдгреВрдирдЪ рдПрдХрд╛ рдбреЗрд╕реНрдХрд╡рд░рдЪрд╛ web bug рдлрдХреНрдд web bug рд░рд╛рд╣рддреЛ.

тПня╕П рдкреБрдвреЗ

рдбреЗрд╕реНрдХ рд╕реБрд░рдХреНрд╖рд┐рдд рдЖрд╣реЗрдд. рдЖрддрд╛, рддреНрдпрд╛рдВрдЪрд╛ рдЦрд░реНрдЪ рдХрд┐рддреА тАФ рдЖрдгрд┐ рддреЛрдЪ рдбреЗрд╕реНрдХ рдЕрд░реНрдзреНрдпрд╛ рдХрд┐рдорддреАрдд рдХрд╕рд╛ рдорд┐рд│реВ рд╢рдХрддреЛ? Pricing: on-demand, Savings Plans рдЖрдгрд┐ spot.

git checkout lesson-08-pricing

ЁЯкк Lesson 07 тАФ Metadata & roles: "who am I?" and the badge

ЁЯУН You are here: Lesson 07 of 12 ┬╖ Previous: lesson-06-connecting ┬╖ Next: lesson-08-pricing


ЁЯУж What's in this branch

Lessons 01тАУ06, plus the instance metadata service (every desk can ask "who am I?"), why IMDSv2 and a hop limit of 1 matter, and the instance profile тАФ the badge that gives a desk short-lived credentials so nobody pastes keys onto it. The lab is ec2/check_template.py, a reviewer that reads a launch template and lists what to fix before any desk is built тАФ tried on ec2/templates/risky.json and ec2/templates/web.json.

ЁЯзТ Explain like I'm 5

Every desk has a little help point on the wall ЁЯЫОя╕П that only that desk can reach. Ask it "which desk am I? which building? what was my first-boot script?" and it answers. If the desk wears a badge ЁЯкк (an instance profile holding a role), the help point can also give the desk day-passes тАФ permissions that expire after a few hours and are renewed automatically. No keys taped under the desk.

The old help point (IMDSv1) answered anyone who passed a note to it. So a trickster could pass a note to a helpful program on the desk тАФ "please ask the help point for the day-pass and read me the answer" тАФ and walk off with it.

The new help point (IMDSv2) first asks: "come here in person and sign in for a session ticket (a token); then show that ticket with every question." A note passed through a helper usually cannot do that sign-in step. And the ticket only travels one step (the hop limit): it cannot be carried further away from the desk than you allow.

ЁЯЧ║я╕П Diagram

flowchart LR
    app["ЁЯЦея╕П program on the desk"]
    sess["1 sign in: ask for a session token<br/>(IMDSv2: HttpTokens = required)"]
    imds["ЁЯЫОя╕П metadata service<br/>link-local, only this desk"]
    q["2 every question carries the token"]
    hop["hop limit 1: the token cannot travel<br/>past the desk (2 only for containers that need it)"]
    badge["ЁЯкк instance profile тЖТ role<br/>short-lived credentials, rotated automatically"]
    app --> sess --> imds
    app --> q --> imds
    imds -.-> hop
    badge -->|"3 SDKs pick these up тАФ you write no code"| app
    check["ЁЯФН check_template.py reads the recipe card first<br/>risky.json: 6 findings ┬╖ web.json: nothing risky"]
    check -.->|"0 before any desk exists"| sess

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

тЭУ What

ЁЯдФ Why

Because stolen long-lived access keys are one of the most common ways AWS accounts are breached, and SSRF against IMDSv1 has turned "a web bug" into "the attacker has the server's role" in real incidents. IMDSv2, a hop limit of 1 and a narrow role close that chain at three places тАФ for the price of three lines in a launch template.

ЁЯФз How (in this repo)

ec2/check_template.py has six rules тАФ IMDSv2 not required, hop limit above 2, an unencrypted volume, no instance profile, a key pair (prefer Session Manager, lesson 06), and gp2 instead of gp3 тАФ and review() returns each finding with its fix. metadata() in ec2/demo.py reviews both templates. It reads JSON only; it never contacts AWS or the metadata service. Test L07 in ec2/test_ec2.py expects exactly 6 findings for risky.json.

ЁЯзк Try it

python3 ec2/demo.py metadata
python3 ec2/check_template.py ec2/templates/risky.json
python3 ec2/check_template.py ec2/templates/web.json
python3 - <<'EOF'
import json, sys; sys.path.insert(0, "ec2")
from check_template import review
t = json.load(open("ec2/templates/risky.json"))
print("before:", len(review(t)), "finding(s)")
# fix the two metadata settings and give the desk a badge тАФ in memory; the file is untouched
t["MetadataOptions"] = {"HttpTokens": "required", "HttpPutResponseHopLimit": 1}
t["IamInstanceProfile"] = {"Name": "school-web"}
left = review(t)
print("after:", len(left), "finding(s):", [name for name, fix in left])
EOF

Your turn: fix the remaining three in the same snippet (encrypt the volume, remove KeyName, switch to gp3) until it prints after: 0 finding(s).

On a real account (needs AWS credentials; these change settings, not credentials тАФ use your own IDs and role name):

aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 \
    --http-tokens required --http-put-response-hop-limit 1 --http-endpoint enabled
aws ec2 describe-instances --instance-ids i-0123456789abcdef0 \
    --query 'Reservations[].Instances[].MetadataOptions'
aws iam create-instance-profile --instance-profile-name school-web
aws iam add-role-to-instance-profile --instance-profile-name school-web --role-name school-web-role
aws ec2 associate-iam-instance-profile --instance-id i-0123456789abcdef0 --iam-instance-profile Name=school-web
aws ec2 modify-instance-metadata-defaults --http-tokens required --http-put-response-hop-limit 2

тЬЕ Verify тАФ what you should see

metadata prints ЁЯУД risky.json: 6 finding(s) with six тЪая╕П lines тАФ metadata service v2 not required, metadata hop limit above 2, root volume not encrypted, no instance profile (the desk has no badge), a key pair for SSH, gp2 instead of gp3 тАФ then ЁЯУД web.json: тЬЕ nothing risky. check_template.py on risky.json ends 6 thing(s) to fix before launching; on web.json it prints тЬЕ nothing risky found. Your snippet prints before: 6 finding(s) and after: 3 finding(s): ['root volume not encrypted', 'a key pair for SSH', 'gp2 instead of gp3'].

ЁЯПБ What you just proved

You can read a launch template and say whether its desks will require session-token metadata, keep the token on the desk, and wear a badge instead of keys тАФ before a single desk exists.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: "IMDSv2 required" is a default check in AWS Security Hub and most audits, and organisations enforce it with SCPs on ec2:MetadataHttpTokens. Combined with narrow roles, it is why a web bug on one desk stays a web bug.

тПня╕П Next

The desks are safe. Now, what do they cost тАФ and how can the same desk cost half as much? Pricing: on-demand, Savings Plans and spot.

git checkout lesson-08-pricing
тЖР PreviousconnectingNext тЖТpricing

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