ЁЯПл The SchoolтА║ЁЯкк IAMтА║ЁЯдЭ рдзрдбрд╛ 09 тАФ Cross-account рдЖрдгрд┐ resource policies: рджреЛрди рджрд░рд╡рд╛рдЬреЗ, рджреЛрди рдЪрд╛рд╡реНрдпрд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯдЭ рдзрдбрд╛ 09 тАФ Cross-account рдЖрдгрд┐ resource policies: рджреЛрди рджрд░рд╡рд╛рдЬреЗ, рджреЛрди рдЪрд╛рд╡реНрдпрд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧреАрд▓: lesson-08-guardrails ┬╖ рдкреБрдвреАрд▓: lesson-10-people-at-scale


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

рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ resource-based policies тАФ рд╡рд╕реНрддреВрд╡рд░рдЪ рдЕрд╕рд▓реЗрд▓реА policy (S3 bucket policy, bucket-policy-reports.json) рдЬреА Principal рдиреЗ рдХреЛрдг рддреЗ рд╕рд╛рдВрдЧрддреЗ тАФ рдЖрдгрд┐ cross-account access, рдЬрд┐рдереЗ partner account рдЪрд╛ role (partner-auditor-identity.json) рд╢рд╛рд│реЗрдЪреЗ reports рд╡рд╛рдЪрддреЛ.

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

Partner рд╢рд╛рд│реЗрдЪреНрдпрд╛ auditor рд▓рд╛ рддреБрдордЪреА рдкреНрд░рдЧрддрд┐рдкреБрд╕реНрддрдХреЗ рд╡рд╛рдЪрд╛рдпрдЪреА рдЖрд╣реЗрдд. рджреЛрди рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдВрдиреА рдорд╛рдиреНрдп рдХрд░рд╛рдпрд▓рд╛ рд╣рд╡реЗ:

рджреЛрди рджрд░рд╡рд╛рдЬреЗ ЁЯЪкЁЯЪк, рджреЛрди рдЪрд╛рд╡реНрдпрд╛ ЁЯФСЁЯФС. рдПрдХрд╛рдЪ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдиреЗ рд╣реЛ рдореНрд╣рдЯрд▓реЗ рддрд░ рддреА рдмрд╛рд╣реЗрд░рдЪ рд░рд╛рд╣рддреЗ. рдПрдХрд╛рдЪ рд╢рд╛рд│реЗрдд рд╣реЗ рд╕реЛрдкреЗ рдЕрд╕рддреЗ: рдХрдкрд╛рдЯрд╛рд╡рд░рдЪреА, рдПрдЦрд╛рджреНрдпрд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдЪреЗ рдирд╛рд╡ рдЕрд╕рд▓реЗрд▓реА рдЪрд┐рдареНрдареА рдПрдХрдЯреАрдЪ рдкреБрд░реЗрд╢реА рдЕрд╕рддреЗ.

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

flowchart LR
    aud["ЁЯХ╡я╕П role/partner-auditor<br/>account 444455556666"]
    idp["ЁЯУЭ her identity policy<br/>s3:GetObject school-reports/*"]
    bp["ЁЯУМ bucket policy on school-reports<br/>Principal: role/partner-auditor"]
    obj["ЁЯУД school-reports/term1.pdf<br/>account 111122223333"]
    aud -->|"1 her account says yes"| idp
    idp -->|"2 the bucket says yes"| bp
    bp -->|"both"| obj
    obj --> ok["тЬЕ ALLOW"]

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

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

ЁЯдФ рдХрд╛

рдЦрд▒реНрдпрд╛ organizations рдХрдбреЗ рдЕрдиреЗрдХ accounts рдЕрд╕рддрд╛рдд (рдкреНрд░рддреНрдпреЗрдХ team рд╕рд╛рдареА, рдкреНрд░рддреНрдпреЗрдХ environment рд╕рд╛рдареА), рд╢рд┐рд╡рд╛рдп partners рдЖрдгрд┐ vendors. Cross-account access рдореБрд│реЗ рддреЗ credentials рди рджреЗрддрд╛ share рдХрд░реВ рд╢рдХрддрд╛рдд тАФ рдЖрдгрд┐ рдЪреБрдХреАрдЪреА рд╡рд╛рдЪрд▓реЗрд▓реА bucket policy ("Principal": "*") рд╣реА public-data рдЧрд│рддреАрдЪреЗ рдХреНрд▓рд╛рд╕рд┐рдХ рдЙрджрд╛рд╣рд░рдг рдЖрд╣реЗ.

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

iam/demo.py рдордзреАрд▓ crossaccount() partner auditor рд╕рд╛рдареА decide() рд▓рд╛ resource_policy= рдЖрдгрд┐ resource_account= (рд╢рд╛рд│реЗрдЪреЗ account) рджреЗрдКрди рдмреЛрд▓рд╛рд╡рддреЗ: рджреЛрдиреНрд╣реА рдмрд╛рдЬреВ, рдлрдХреНрдд bucket, рдлрдХреНрдд identity, рдЖрдгрд┐ рдПрдХ delete. iam/evaluate.py рдордзреАрд▓ _principal_ok() resource policy рдЪрд╛ Principal рддрдкрд╛рд╕рддреЗ, account рдкреНрд░рдХрд╛рд░ тАж:root рд╕рдХрдЯ.

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

python3 iam/demo.py crossaccount
python3 - <<'EOF'
import sys; sys.path.insert(0, "iam"); from evaluate import decide, load
R, SCHOOL = "arn:aws:s3:::school-reports/term1.pdf", "111122223333"
ident = [("partner-auditor-identity", load("partner-auditor-identity"))]
named = ("bucket-policy-reports", load("bucket-policy-reports"))
whole = ("bucket-policy-account", {"Version": "2012-10-17", "Statement": [{"Sid": "TrustPartnerAccount", "Effect": "Allow",
         "Principal": {"AWS": "arn:aws:iam::444455556666:root"}, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::school-reports/*"}]})
intern = "arn:aws:iam::444455556666:role/partner-intern"
print("intern, bucket names auditor ", decide(intern, "s3:GetObject", R, identity=ident, resource_policy=named, resource_account=SCHOOL)[0])
print("intern, bucket trusts account", decide(intern, "s3:GetObject", R, identity=ident, resource_policy=whole, resource_account=SCHOOL)[0])
print("intern, account, no own grant", decide(intern, "s3:GetObject", R, identity=[], resource_policy=whole, resource_account=SCHOOL)[0])
EOF

рдЦрд▒реНрдпрд╛ account рд╡рд░ (AWS CLI рдЖрдгрд┐ credentials рд▓рд╛рдЧрддрд╛рдд; S3 policy рд╕реНрд╡реАрдХрд╛рд░рдгреНрдпрд╛рдЖрдзреА role arn:aws:iam::444455556666:role/partner-auditor рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡рд╛):

aws s3api put-bucket-policy --bucket school-reports --policy file://iam/policies/bucket-policy-reports.json
aws s3api get-bucket-policy --bucket school-reports --query Policy --output text
aws s3 cp s3://school-reports/term1.pdf . --profile partner-auditor      # run as the partner's role

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

crossaccount рдЫрд╛рдкрддреЗ тЬЕ ALLOW тАФ cross-account: identity policy partner-auditor-identity#ReadSchoolReports AND resource policy bucket-policy-reports#PartnerAuditorReads both allow, рдордЧ рддреАрди ЁЯЪл IMPLICIT DENY рдУрд│реА: рдлрдХреНрдд bucket (the caller's identity policy does not), рдлрдХреНрдд identity (the resource policy in the other account does not), рдЖрдгрд┐ DeleteObject. рддреБрдордЪрд╛ snippet рдЫрд╛рдкрддреЛ IMPLICIT DENY (bucket auditor рдЪреЗ рдирд╛рд╡ рдШреЗрддреЗ, intern рдЪреЗ рдирд╛рд╣реА), ALLOW (bucket рд╕рдВрдкреВрд░реНрдг partner account рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рддреЗ, рдЖрдгрд┐ partner рдЪреА рд╕реНрд╡рддрдГрдЪреА policy intern рд▓рд╛ рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЗ) рдЖрдгрд┐ IMPLICIT DENY (account рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдгреЗ рдкреБрд░реЗрд╕реЗ рдирд╛рд╣реА тАФ partner рдиреЗрд╣реА рддреА рдкрд░рд╡рд╛рдирдЧреА рджреНрдпрд╛рдпрд▓рд╛ рд╣рд╡реА).

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

Accounts рдУрд▓рд╛рдВрдбрддрд╛рдирд╛ рджреЛрдиреНрд╣реА рдмрд╛рдЬреВрдВрдиреА рд╣реЛ рдореНрд╣рдгрд╛рдпрд▓рд╛ рд╣рд╡реЗ; тАж:root рдЪреЗ рдирд╛рд╡ рдШреЗрддрд▓реНрдпрд╛рдиреЗ рдХреЛрдг рд╣реЗ рдирд┐рд╡рдбрдгреНрдпрд╛рдЪреЗ рдХрд╛рдо рджреБрд╕рд▒реНрдпрд╛ account рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ policies рдХрдбреЗ рд╕реЛрдкрд╡рд▓реЗ рдЬрд╛рддреЗ.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: IAM Access Analyzer рдЪреЗ external access findings рддреБрдордЪреНрдпрд╛ account рдХрд┐рдВрд╡рд╛ organization рдмрд╛рд╣реЗрд░ share рдХреЗрд▓реЗрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ resource рджрд╛рдЦрд╡рддрд╛рдд тАФ рддреЗ рддрдкрд╛рд╕рдгреЗ рд╣реЗ рдХрд╛рдпрдордЪреЗ рдХрд╛рдо рдЖрд╣реЗ, рдЖрдгрд┐ bucket public рдЕрд╕рд╛рдпрд▓рд╛рдЪ рд╣рд╡реЗ рдЕрд╕реЗ рдирд╕реЗрд▓ рддрд░ S3 Block Public Access рдЪрд╛рд▓реВрдЪ рд░рд╛рд╣рддреЗ.

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

рдбрдЭрдирднрд░ accounts рдордзреАрд▓ рд╢реЗрдХрдбреЛ рдХрд░реНрдордЪрд╛рд▒реНрдпрд╛рдВрд╕рд╛рдареА IAM users рд╡рд╛рдврддреНрдпрд╛ рдкреНрд░рдорд╛рдгрд╛рдд рдЪрд╛рд▓рдд рдирд╛рд╣реАрдд. рд▓реЛрдХ рдореЛрдареНрдпрд╛ рдкреНрд░рдорд╛рдгрд╛рд╡рд░ тАФ IAM Identity Center.

git checkout lesson-10-people-at-scale

ЁЯдЭ Lesson 09 тАФ Cross-account & resource policies: two doors, two keys

ЁЯУН You are here: Lesson 09 of 12 ┬╖ Previous: lesson-08-guardrails ┬╖ Next: lesson-10-people-at-scale


ЁЯУж What's in this branch

Lessons 01тАУ08, plus resource-based policies тАФ a policy on the thing (an S3 bucket policy, bucket-policy-reports.json) that names who with Principal тАФ and cross-account access, where a partner account's role (partner-auditor-identity.json) reads the school's reports.

ЁЯзТ Explain like I'm 5

The partner school's auditor wants to read your report cards. Two offices must agree:

Two doors ЁЯЪкЁЯЪк, two keys ЁЯФСЁЯФС. If only one office says yes, she stays outside. Inside one school it is easier: a note on the cupboard that names a teacher is enough on its own.

ЁЯЧ║я╕П Diagram

flowchart LR
    aud["ЁЯХ╡я╕П role/partner-auditor<br/>account 444455556666"]
    idp["ЁЯУЭ her identity policy<br/>s3:GetObject school-reports/*"]
    bp["ЁЯУМ bucket policy on school-reports<br/>Principal: role/partner-auditor"]
    obj["ЁЯУД school-reports/term1.pdf<br/>account 111122223333"]
    aud -->|"1 her account says yes"| idp
    idp -->|"2 the bucket says yes"| bp
    bp -->|"both"| obj
    obj --> ok["тЬЕ ALLOW"]

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

тЭУ What

ЁЯдФ Why

Real organizations have many accounts (per team, per environment) plus partners and vendors. Cross-account access is how they share without sharing credentials тАФ and a misread bucket policy ("Principal": "*") is the classic public-data leak.

ЁЯФз How (in this repo)

crossaccount() in iam/demo.py calls decide() with resource_policy= and resource_account= (the school's account) for the partner auditor: both sides, bucket only, identity only, and a delete. _principal_ok() in iam/evaluate.py checks the Principal of a resource policy, including the account form тАж:root.

ЁЯзк Try it

python3 iam/demo.py crossaccount
python3 - <<'EOF'
import sys; sys.path.insert(0, "iam"); from evaluate import decide, load
R, SCHOOL = "arn:aws:s3:::school-reports/term1.pdf", "111122223333"
ident = [("partner-auditor-identity", load("partner-auditor-identity"))]
named = ("bucket-policy-reports", load("bucket-policy-reports"))
whole = ("bucket-policy-account", {"Version": "2012-10-17", "Statement": [{"Sid": "TrustPartnerAccount", "Effect": "Allow",
         "Principal": {"AWS": "arn:aws:iam::444455556666:root"}, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::school-reports/*"}]})
intern = "arn:aws:iam::444455556666:role/partner-intern"
print("intern, bucket names auditor ", decide(intern, "s3:GetObject", R, identity=ident, resource_policy=named, resource_account=SCHOOL)[0])
print("intern, bucket trusts account", decide(intern, "s3:GetObject", R, identity=ident, resource_policy=whole, resource_account=SCHOOL)[0])
print("intern, account, no own grant", decide(intern, "s3:GetObject", R, identity=[], resource_policy=whole, resource_account=SCHOOL)[0])
EOF

On a real account (needs the AWS CLI and credentials; the role arn:aws:iam::444455556666:role/partner-auditor must exist before S3 accepts the policy):

aws s3api put-bucket-policy --bucket school-reports --policy file://iam/policies/bucket-policy-reports.json
aws s3api get-bucket-policy --bucket school-reports --query Policy --output text
aws s3 cp s3://school-reports/term1.pdf . --profile partner-auditor      # run as the partner's role

тЬЕ Verify тАФ what you should see

crossaccount prints тЬЕ ALLOW тАФ cross-account: identity policy partner-auditor-identity#ReadSchoolReports AND resource policy bucket-policy-reports#PartnerAuditorReads both allow, then three ЁЯЪл IMPLICIT DENY lines: bucket only (the caller's identity policy does not), identity only (the resource policy in the other account does not), and DeleteObject. Your snippet prints IMPLICIT DENY (the bucket names the auditor, not the intern), ALLOW (the bucket trusts the whole partner account, and the partner's own policy lets the intern) and IMPLICIT DENY (trusting the account is not enough тАФ the partner must also grant it).

ЁЯПБ What you just proved

Across accounts, both sides must say yes; naming тАж:root delegates the choice of who to the other account's own policies.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: IAM Access Analyzer's external access findings list every resource shared outside your account or organization тАФ reviewing them is a standing task, and S3 Block Public Access stays on unless a bucket is meant to be public.

тПня╕П Next

IAM users for hundreds of staff across dozens of accounts do not scale. People at scale тАФ IAM Identity Center.

git checkout lesson-10-people-at-scale
тЖР PreviousguardrailsNext тЖТpeople at scale

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