ЁЯПл The SchoolтА║ЁЯкк IAMтА║ЁЯдЦ рдзрдбрд╛ 06 тАФ Machine identities: secrets рд╢рд┐рд╡рд╛рдп рдмрд┐рд▓реНрд▓реЗ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯдЦ рдзрдбрд╛ 06 тАФ Machine identities: secrets рд╢рд┐рд╡рд╛рдп рдмрд┐рд▓реНрд▓реЗ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 06 ┬╖ рдорд╛рдЧреЗ: lesson-05-roles-sts ┬╖ рдкреБрдвреЗ: lesson-07-conditions-abac


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

рдзрдбреЗ 01тАУ05, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд software рд▓рд╛ рд╕рд╛рдард╡рд▓реЗрд▓реНрдпрд╛ key рд╢рд┐рд╡рд╛рдп AWS access рдХрд╕рд╛ рдорд┐рд│рддреЛ: GitHub Actions рд▓рд╛ OIDC federation рдордзреВрди (trust-github-oidc.json), рдЖрдгрд┐ рд╣реАрдЪ рдХрд▓реНрдкрдирд╛ AWS compute рд╡рд░ тАФ EC2 instance profiles, Lambda execution roles, EKS IRSA рдЖрдгрд┐ Pod Identity.

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

рд╢рд╛рд│реЗрдЪрд╛ delivery robot ЁЯдЦ рдЧрд╛рд╡рд╛рдЪреНрдпрд╛ рдкрд▓реАрдХрдбрдЪреНрдпрд╛ рдПрдХрд╛ company рдХрдбреВрди рдпреЗрддреЛ. рддреНрдпрд╛рд▓рд╛ рдорд╛рд╕реНрдЯрд░ рдЪрд╛рд╡реАрдЪреА рдкреНрд░рдд рджреЗрдгреЗ рд╣реА рд╡рд╛рдИрдЯ рдХрд▓реНрдкрдирд╛ рдЖрд╣реЗ тАФ robots рд╣рд░рд╡рддрд╛рдд, рдЪрд╛рд╡реНрдпрд╛рдВрдЪреНрдпрд╛ рдкреНрд░рддреА рдмрдирддрд╛рдд.

рддреНрдпрд╛рдРрд╡рдЬреА robot рдЪреА company рддреНрдпрд╛рд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдлреЗрд░реАрд╕рд╛рдареА рдПрдХ рд╕рд╣реА рдХреЗрд▓реЗрд▓реЗ рдкрддреНрд░ рджреЗрддреЗ: "рд╣рд╛ robot 7 рдЖрд╣реЗ, рдЖрдЬ main delivery рдорд╛рд░реНрдЧрд╛рд╡рд░ рдХрд╛рдо рдХрд░рддреЛ рдЖрд╣реЗ". рдУрд│рдЦрдкрддреНрд░ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рд▓рд╛ рддреНрдпрд╛ company рдЪреА рд╕рд╣реА рдорд╛рд╣реАрдд рдЕрд╕рддреЗ, рддреЗ рдкрддреНрд░ рд╡рд╛рдЪрддреЗ, рддреНрдпрд╛рд╡рд░ рд▓рд┐рд╣рд┐рд▓реЗрд▓рд╛ рдорд╛рд░реНрдЧ рддрдкрд╛рд╕рддреЗ, рдЖрдгрд┐ robot рд▓рд╛ рдПрдХрд╛ рддрд╛рд╕рд╛рдЪреА рдЯреЛрдкреА рджреЗрддреЗ. рд╡реЗрдЧрд│реНрдпрд╛ рдорд╛рд░реНрдЧрд╛рд╕рд╛рдареАрдЪреЗ рдкрддреНрд░, рдХрд┐рдВрд╡рд╛ рд╡реЗрдЧрд│реНрдпрд╛ company рдХрдбреВрди рдЖрд▓реЗрд▓реЗ рдкрддреНрд░, рдпрд╛рдВрдирд╛ рдХрд╛рд╣реАрдЪ рдорд┐рд│рдд рдирд╛рд╣реА.

рддреЗ рд╕рд╣реА рдХреЗрд▓реЗрд▓реЗ рдкрддреНрд░ рдореНрд╣рдгрдЬреЗ OIDC token. GitHub рдкреНрд░рддреНрдпреЗрдХ workflow run рд╕рд╛рдареА рдПрдХ token рд╕рд╣реА рдХрд░реВрди рджреЗрддреЛ; AWS рддреЛ role рдЪреНрдпрд╛ trust policy рд╢реА рддрдкрд╛рд╕рддреЛ рдЖрдгрд┐ рддрд╛рддреНрдкреБрд░рддреА credentials рджреЗрддреЛ. GitHub рдордзреНрдпреЗ рдХреЛрдгрддреЗрд╣реА secret рдХрдзреАрдЪ рд╕рд╛рдард╡рд▓реЗ рдЬрд╛рдд рдирд╛рд╣реА.

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

flowchart LR
    gh["тЪЩя╕П GitHub Actions run<br/>id-token: write"]
    tok["тЬЙя╕П signed OIDC token<br/>aud: sts.amazonaws.com<br/>sub: repo:BaluRaut/school-app:ref:refs/heads/main"]
    sts["ЁЯОЯя╕П STS AssumeRoleWithWebIdentity"]
    trust["ЁЯП╖я╕П trust policy<br/>Federated: token.actions.githubusercontent.com<br/>checks aud + sub"]
    creds["ЁЯОй 1-hour credentials"]
    gh -->|"1 asks GitHub"| tok -->|"2 presents"| sts --> trust
    trust -->|"3 claims match"| creds
    trust -.->|"feature branch, fork"| no["ЁЯЪл refused"]

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

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

ЁЯдФ рдХрд╛

Secret рдореНрд╣рдгреВрди рд╕рд╛рдард╡рд▓реЗрд▓реА CI access key рдореНрд╣рдгрдЬреЗ рджреАрд░реНрдШрдХрд╛рд│рд╛рдЪреА рдорд╛рд╕реНрдЯрд░ рдЪрд╛рд╡реА, рдЬреА рдкреНрд░рддреНрдпреЗрдХ workflow рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ maintainer рд▓рд╛ рдорд┐рд│реВ рд╢рдХрддреЗ, рдЖрдгрд┐ рддреА рд╣рд╛рддрд╛рдиреЗ rotate рдХрд░рд╛рд╡реА рд▓рд╛рдЧрддреЗ. Federation рддрд┐рдЪреНрдпрд╛ рдЬрд╛рдЧреА рдкреНрд░рддреНрдпреЗрдХ run рд╕рд╛рдареАрдЪреЗ credential рдЖрдгрддреЗ, рдЬреЗ рдПрдХрд╛ рддрд╛рд╕рд╛рдд рд╕рдВрдкрддреЗ рдЖрдгрд┐ рддреБрдореНрд╣реА рдирд╛рд╡ рджрд┐рд▓реЗрд▓реНрдпрд╛ repo рдЖрдгрд┐ branch рд╕рд╛рдареАрдЪ рдЪрд╛рд▓рддреЗ.

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

iam/demo.py рдордзрд▓рд╛ machines() token claims рдЪреЗ рддреАрди рд╕рдВрдЪ рдмрдирд╡рддреЛ тАФ main branch, рдПрдХ feature branch рдЖрдгрд┐ рдПрдХ fork тАФ рдЖрдгрд┐ trust statement рдЪреНрдпрд╛ conditions matches() рдордзреВрди рдЪрд╛рд▓рд╡рддреЛ. рдлрдХреНрдд main рд▓рд╛ role рдорд┐рд│рддреЛ.

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

python3 iam/demo.py machines
python3 - <<'EOF'
import json, sys; sys.path.insert(0, "iam"); from evaluate import load, matches
trust = load("trust-github-oidc")["Statement"][0]
def token(sub, aud="sts.amazonaws.com"):
    return {"token.actions.githubusercontent.com:aud": aud, "token.actions.githubusercontent.com:sub": sub}
A = "sts:AssumeRoleWithWebIdentity"
print("main, right audience :", matches(trust, A, "*", token("repo:BaluRaut/school-app:ref:refs/heads/main")))
print("main, wrong audience :", matches(trust, A, "*", token("repo:BaluRaut/school-app:ref:refs/heads/main", aud="my-app")))
print("pull request         :", matches(trust, A, "*", token("repo:BaluRaut/school-app:pull_request")))
loose = json.loads(json.dumps(trust).replace("ref:refs/heads/main", "*"))
print("pull request, loose  :", matches(loose, A, "*", token("repo:BaluRaut/school-app:pull_request")))
EOF

рдЦрд▒реНрдпрд╛ account рд╡рд░ (AWS CLI рдЖрдгрд┐ credentials рд▓рд╛рдЧрддрд╛рдд) тАФ GitHub рдЪреА рдПрдХрджрд╛ рдиреЛрдВрджрдгреА рдХрд░рд╛, рдЖрдгрд┐ рдпрд╛ trust policy рд╕рд╣ role рдмрдирд╡рд╛:

aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com
aws iam create-role --role-name github-deployer --assume-role-policy-document file://iam/policies/trust-github-oidc.json

рдЖрдгрд┐ workflow рдордзреНрдпреЗ (GitHub рдЪреА рдмрд╛рдЬреВ):

permissions:
  id-token: write        # lets the job ask GitHub for an OIDC token
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::111122223333:role/github-deployer
      aws-region: ap-south-1
  - run: aws sts get-caller-identity

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

machines рд╣реЗ print рдХрд░рддреЛ: тЬЕ тАж refs/heads/main тЖТ role assumed, 1-hour credentials рдЖрдгрд┐ feature-x рд╕рд╛рдареА рд╡ repo:someone/fork рд╕рд╛рдареА ЁЯЪл тАж refused. рддреБрдордЪрд╛ snippet True, False (рдЪреБрдХреАрдЪрд╛ audience), False (pull request рдореНрд╣рдгрдЬреЗ main рдирд╛рд╣реА) рдЖрдгрд┐ рд╕реИрд▓ рдХреЗрд▓реЗрд▓реНрдпрд╛ repo:BaluRaut/school-app:* рд╕рд╛рдареА True print рдХрд░рддреЛ тАФ wildcard рдЕрд╕рд▓реЗрд▓рд╛ sub рдХреЛрдгрддреНрдпрд╛рд╣реА branch рд▓рд╛ рдХрд┐рдВрд╡рд╛ pull request рд▓рд╛ deploy рдХрд░реВ рджреЗрддреЛ. рдЦрд▒реНрдпрд╛ workflow рдордзреНрдпреЗ get-caller-identity assumed-role/github-deployer/тАж рджрд╛рдЦрд╡рддреЛ.

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

Machine рд╕рд╛рдард╡рд▓реЗрд▓реНрдпрд╛ secret рдРрд╡рдЬреА рд╕рд╣реА рдХреЗрд▓реЗрд▓реНрдпрд╛, рдЕрд▓реНрдкрдХрд╛рд│рд╛рдЪреНрдпрд╛ token рдиреЗ рдЖрдкрдг рдХреЛрдг рдЖрд╣реЛрдд рд╣реЗ рд╕рд┐рджреНрдз рдХрд░реВ рд╢рдХрддреЗ тАФ рдЖрдгрд┐ рдХреЛрдгрддреЗ runs рдкрд╛рддреНрд░ рдард░рддрд╛рдд рд╣реЗ trust policy рдЪреА sub condition рдиреЗрдордХреЗ рдард░рд╡рддреЗ.

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

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: "CI рдордзреНрдпреЗ рджреАрд░реНрдШрдХрд╛рд│рд╛рдЪреНрдпрд╛ keys рдирд╛рд╣реАрдд" рд╣рд╛ рдЖрддрд╛ audit рдЪрд╛ рдиреЗрд╣рдореАрдЪрд╛ рдореБрджреНрджрд╛ рдЖрд╣реЗ. CI рд╕рд╛рдареА OIDC, AWS compute рд╕рд╛рдареА instance/task/execution roles, Kubernetes рд╕рд╛рдареА IRSA рдХрд┐рдВрд╡рд╛ Pod Identity тАФ рдЖрдгрд┐ credential report рдордзрд▓реА keys рдЪреА рд╕рдВрдЦреНрдпрд╛ рд╢реВрдиреНрдпрд╛рд╡рд░ рдпреЗрддреЗ.

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

рддреНрдпрд╛ рдХреНрд╖рдгрд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕рдгрд╛рд░реЗ рдирд┐рдпрдо тАФ MFA, network, рдПрдЦрд╛рджрд╛ tag. Conditions рдЖрдгрд┐ ABAC.

git checkout lesson-07-conditions-abac

ЁЯдЦ Lesson 06 тАФ Machine identities: badges without secrets

ЁЯУН You are here: Lesson 06 of 12 ┬╖ Previous: lesson-05-roles-sts ┬╖ Next: lesson-07-conditions-abac


ЁЯУж What's in this branch

Lessons 01тАУ05, plus how software gets AWS access with no stored key: GitHub Actions through OIDC federation (trust-github-oidc.json), and the same idea on AWS compute тАФ EC2 instance profiles, Lambda execution roles, EKS IRSA and Pod Identity.

ЁЯзТ Explain like I'm 5

The school's delivery robot ЁЯдЦ comes from a company across town. Giving it a copy of the master key is a bad idea тАФ robots get lost, keys get copied.

Instead, the robot's company gives it a signed letter for each trip: "this is robot 7, working on the main delivery route, today". The ID office knows the company's signature, reads the letter, checks the route written on it, and gives the robot a one-hour hat. A letter for a different route, or from a different company, gets nothing.

That signed letter is an OIDC token. GitHub signs one for every workflow run; AWS checks it against the role's trust policy and issues temporary credentials. Nothing secret is ever stored in GitHub.

ЁЯЧ║я╕П Diagram

flowchart LR
    gh["тЪЩя╕П GitHub Actions run<br/>id-token: write"]
    tok["тЬЙя╕П signed OIDC token<br/>aud: sts.amazonaws.com<br/>sub: repo:BaluRaut/school-app:ref:refs/heads/main"]
    sts["ЁЯОЯя╕П STS AssumeRoleWithWebIdentity"]
    trust["ЁЯП╖я╕П trust policy<br/>Federated: token.actions.githubusercontent.com<br/>checks aud + sub"]
    creds["ЁЯОй 1-hour credentials"]
    gh -->|"1 asks GitHub"| tok -->|"2 presents"| sts --> trust
    trust -->|"3 claims match"| creds
    trust -.->|"feature branch, fork"| no["ЁЯЪл refused"]

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

тЭУ What

ЁЯдФ Why

A CI access key stored as a secret is a long-lived master key that every workflow and every maintainer can reach, and it has to be rotated manually. Federation replaces it with a per-run credential that expires in an hour and only works for the repo and branch you named.

ЁЯФз How (in this repo)

machines() in iam/demo.py builds three sets of token claims тАФ the main branch, a feature branch and a fork тАФ and runs the trust statement's conditions through matches(). Only main gets the role.

ЁЯзк Try it

python3 iam/demo.py machines
python3 - <<'EOF'
import json, sys; sys.path.insert(0, "iam"); from evaluate import load, matches
trust = load("trust-github-oidc")["Statement"][0]
def token(sub, aud="sts.amazonaws.com"):
    return {"token.actions.githubusercontent.com:aud": aud, "token.actions.githubusercontent.com:sub": sub}
A = "sts:AssumeRoleWithWebIdentity"
print("main, right audience :", matches(trust, A, "*", token("repo:BaluRaut/school-app:ref:refs/heads/main")))
print("main, wrong audience :", matches(trust, A, "*", token("repo:BaluRaut/school-app:ref:refs/heads/main", aud="my-app")))
print("pull request         :", matches(trust, A, "*", token("repo:BaluRaut/school-app:pull_request")))
loose = json.loads(json.dumps(trust).replace("ref:refs/heads/main", "*"))
print("pull request, loose  :", matches(loose, A, "*", token("repo:BaluRaut/school-app:pull_request")))
EOF

On a real account (needs the AWS CLI and credentials) тАФ register GitHub once, create the role with this trust policy:

aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com
aws iam create-role --role-name github-deployer --assume-role-policy-document file://iam/policies/trust-github-oidc.json

and in the workflow (GitHub side):

permissions:
  id-token: write        # lets the job ask GitHub for an OIDC token
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::111122223333:role/github-deployer
      aws-region: ap-south-1
  - run: aws sts get-caller-identity

тЬЕ Verify тАФ what you should see

machines prints тЬЕ тАж refs/heads/main тЖТ role assumed, 1-hour credentials and ЁЯЪл тАж refused for feature-x and for repo:someone/fork. Your snippet prints True, False (wrong audience), False (a pull request is not main) and True for the loosened repo:BaluRaut/school-app:* тАФ a wildcard sub lets any branch or pull request deploy. In a real workflow, get-caller-identity shows assumed-role/github-deployer/тАж.

ЁЯПБ What you just proved

A machine can prove who it is with a signed, short-lived token instead of a stored secret тАФ and the trust policy's sub condition decides exactly which runs qualify.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: "no long-lived keys in CI" is now a standard audit item. OIDC for CI, instance/task/execution roles for AWS compute, IRSA or Pod Identity for Kubernetes тАФ and the credential-report key count goes to zero.

тПня╕П Next

Rules that depend on the moment тАФ MFA, the network, a tag. Conditions and ABAC.

git checkout lesson-07-conditions-abac
тЖР Previousroles stsNext тЖТconditions abac

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