ЁЯПл The SchoolтА║ЁЯУо CI/CDтА║ЁЯкк рдзрдбрд╛ 06 тАФ Secrets рдЖрдгрд┐ least privilege: рдХреБрд░рд┐рдЕрд░рдЪрд╛ рдмреЕрдЬ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯкк рдзрдбрд╛ 06 тАФ Secrets рдЖрдгрд┐ least privilege: рдХреБрд░рд┐рдЕрд░рдЪрд╛ рдмреЕрдЬ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 06 ┬╖ рдорд╛рдЧреЗ: lesson-05-artifacts-reports ┬╖ рдкреБрдвреЗ: lesson-07-build-push-image


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

рдзрдбреЗ 01тАУ05, рдЖрдгрд┐ mailroom рд╕реБрд░рдХреНрд╖рд┐рдд рдареЗрд╡рдгрд╛рд░рд╛ рдирд┐рдпрдо тАФ courier рд╢рд╛рд│реЗрдЪреА рдореБрдЦреНрдп рдХрд┐рд▓реНрд▓реА рдмрд╛рд│рдЧрдд рдирд╛рд╣реА. рдЦрд▒реНрдпрд╛ files:

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

рдлреЛрдЯреЛрдХреЙрдкреА рдареЗрд╡рдгреНрдпрд╛рд╕рд╛рдареА courier robot ЁЯУо рд▓рд╛ рд▓реЙрдХрд░ рд░реВрдо ЁЯПж рдордзреНрдпреЗ рдЬрд╛рдпрдЪреЗ рдЖрд╣реЗ. рдЖрд│рд╢реА рдорд╛рд░реНрдЧ: рддреНрдпрд╛рд▓рд╛ рдореБрдЦреНрдп рдХрд┐рд▓реНрд▓реА ЁЯФС рджреНрдпрд╛ тАФ рддреА рд╕рдВрдкреВрд░реНрдг рдЗрдорд╛рд░рдд рдЙрдШрдбрддреЗ, рддрд┐рд▓рд╛ рд╕рдВрдкрдгреНрдпрд╛рдЪреА рддрд╛рд░реАрдЦ рдирд╛рд╣реА, рдЖрдгрд┐ robot рдиреЗ рддреА рдХреЙрд░рд┐рдбреЙрд░рдордзреНрдпреЗ рдЯрд╛рдХрд▓реА рддрд░ рдЬреНрдпрд╛рд▓рд╛ рд╕рд╛рдкрдбреЗрд▓ рддреЛ рд╢рд╛рд│реЗрдЪрд╛ рдорд╛рд▓рдХ рд╣реЛрддреЛ. CI рдордзреНрдпреЗ рддреА рдХрд┐рд▓реНрд▓реА рдореНрд╣рдгрдЬреЗ рд╕рд╛рдард╡рд▓реЗрд▓реА AWS access key.

рд╢рд╛рд│реЗрдЪрд╛ рдорд╛рд░реНрдЧ: robot рдПрдХ рдмреЕрдЬ ЁЯкк рдШрд╛рд▓рддреЛ рдЬреНрдпрд╛рд╡рд░ рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ рдЕрд╕рддреЗ "рдпрд╛ рд╢рд╛рд│реЗрдЪрд╛ courier, рдпрд╛ рдорд╛рд░реНрдЧрд╛рд╡рд░". рдкрд╣рд╛рд░реЗрдХрд░реА рддреЛ рдкрд╛рд╣реБрдгреНрдпрд╛рдВрдЪреНрдпрд╛ рдпрд╛рджреАрд╢реА рдЬреБрд│рд╡реВрди рдкрд╛рд╣рддреЛ тАФ рд╢рд╛рд│реЗрдЪреНрдпрд╛ office рдиреЗ рд╕рд╣реА рдХреЗрд▓реА рдЖрд╣реЗ рдХрд╛? рдпрд╛ рджрд╛рд░рд╛рд╕рд╛рдареА рдЖрд╣реЗ рдХрд╛? рдпрд╛ рдорд╛рд░реНрдЧрд╛рд╕рд╛рдареА? тАФ рдЖрдгрд┐ рдПрдХ рдбреЗ рдкрд╛рд╕ ЁЯОл рджреЗрддреЛ рдЬреЛ рдПрдХрдЪ рд▓реЙрдХрд░ рдЙрдШрдбрддреЛ рдЖрдгрд┐ рд╕реБрдорд╛рд░реЗ рдПрдХрд╛ рддрд╛рд╕рд╛рдд рдЪрд╛рд▓рдгреЗ рдмрдВрдж рд╣реЛрддреЛ. рддреЗрдЪ рдореНрд╣рдгрдЬреЗ OIDC: GitHub рдмреЕрдЬрд╡рд░ рд╕рд╣реА рдХрд░рддреЗ, AWS STS рд╣рд╛ рдкрд╣рд╛рд░реЗрдХрд░реА, trust policy рдореНрд╣рдгрдЬреЗ рдкрд╛рд╣реБрдгреНрдпрд╛рдВрдЪреА рдпрд╛рджреА, permissions policy рдореНрд╣рдгрдЬреЗ рдкрд╛рд╕ рдЙрдШрдбреВ рд╢рдХрдгрд╛рд▒реНрдпрд╛ рджрд╛рд░рд╛рдВрдЪреА рдпрд╛рджреА.

Courier рдЪреНрдпрд╛ рдЦрд┐рд╢рд╛рдд рдЖрдгрдЦреА рджреЛрди рдЧреЛрд╖реНрдЯреА. Variables рдореНрд╣рдгрдЬреЗ рд╕реВрдЪрдирд╛ рдлрд▓рдХрд╛рд╡рд░ рд▓рд╛рд╡рд▓реЗрд▓реНрдпрд╛ рдЪрд┐рдареНрдареНрдпрд╛ тАФ region, repository рдЪреЗ рдирд╛рд╡ тАФ рдХреЛрдгреАрд╣реА рд╡рд╛рдЪреВ рд╢рдХрддреЗ. Secrets рдореНрд╣рдгрдЬреЗ рд╕реАрд▓рдмрдВрдж рдкрд╛рдХрд┐рдЯреЗ, рдЬреА mailroom рддреА рдЫрд╛рдкрдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ logs рдордзреНрдпреЗ рдХрд╛рд│реА рдХрд░рддреЗ. рдкрдг marker рдлрдХреНрдд рдиреЗрдордХрд╛ рдордЬрдХреВрд░рдЪ рдУрд│рдЦрддреЛ; рдкрддреНрд░рд╛рдЪреА рддрд┐рд░рдХреА рдлреЛрдЯреЛрдХреЙрдкреА рдХрд╛рдврд╛ (base64 рдХрд░рд╛, рддреБрдХрдбреЗ рдХрд░рд╛, file рдордзреНрдпреЗ рд▓рд┐рд╣рд╛) рдЖрдгрд┐ marker рдЪреБрдХрддреЛ.

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

flowchart LR
    job["ЁЯУо push job<br/>permissions: id-token: write"]
    gh["ЁЯПл GitHub OIDC<br/>signs a short-lived token"]
    sts["ЁЯЫВ AWS STS<br/>checks the trust policy (WHO)<br/>aud + sub"]
    ecr["ЁЯПж ECR hello-courier<br/>ci-role-policy (WHAT): one repo, one cluster"]
    job -->|"1 ask for a badge"| gh
    gh -->|"2 signed ID token"| job
    job -->|"3 show the badge"| sts
    sts -->|"4 day pass: temporary credentials, this job only"| ecr

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

ЁЯза рдореБрдЦреНрдп рдХрд┐рд▓реНрд▓реА рд╡рд┐рд░реБрджреНрдз рдмреЕрдЬ

ЁЯФС рд╕рд╛рдард╡рд▓реЗрд▓реНрдпрд╛ access keys ЁЯкк рдмреЕрдЬ тЖТ ЁЯОл рдбреЗ рдкрд╛рд╕ (OIDC)
рддреЗ рдХреБрдареЗ рд░рд╛рд╣рддреЗ secret рдордзреНрдпреЗ, рдХреЛрдгреА rotate рдХрд░реЗрдкрд░реНрдпрдВрдд рдХреБрдареЗрдЪ рдирд╛рд╣реА тАФ рдкреНрд░рддреНрдпреЗрдХ job рд╕рд╛рдареА рдирд╡реЗ рдмрдирддреЗ
рддреЗ leak рдЭрд╛рд▓реЗ рддрд░ рд▓рдХреНрд╖рд╛рдд рдпреЗрдКрди rotate рд╣реЛрдИрдкрд░реНрдпрдВрдд рд╡реИрдз рддрд╛рд╕рд╛рднрд░рд╛рдд рд╕рдВрдкрддреЗ; trust policy рдЪреНрдпрд╛ рдмрд╛рд╣реЗрд░ рдирд┐рд░реБрдкрдпреЛрдЧреА
рддреЗ рдХрд╛рдп рдЙрдШрдбрддреЗ IAM user рдХрдбреЗ рдЬреЗ рдХрд╛рд╣реА рдЖрд╣реЗ рддреЗ рдлрдХреНрдд role рдЪреНрдпрд╛ policy рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ

ЁЯдФ рдХрд╛

Leak рдЭрд╛рд▓реЗрд▓реА CI credentials рд╣реА рдПрдХ рдиреЗрд╣рдореАрдЪреА рдШрдЯрдирд╛ рдЖрд╣реЗ: log рдордзреНрдпреЗ рдЫрд╛рдкрд▓реЗрд▓реА key, artifact рдордзреНрдпреЗ copy рдЭрд╛рд▓реЗрд▓реА, рдХрд┐рдВрд╡рд╛ fork рдордзрд▓реНрдпрд╛ workflow рдиреЗ рд╡рд╛рдЪрд▓реЗрд▓реА. рд╕рд╛рдард╡рд▓реЗрд▓реА key rotate рдХрд░рд╛рд╡реА рд▓рд╛рдЧрддреЗ, рдЖрдгрд┐ рдкреНрд░рддреНрдпрдХреНрд╖рд╛рдд рдХреНрд╡рдЪрд┐рддрдЪ рдХреЗрд▓реА рдЬрд╛рддреЗ. OIDC рд╕рд╛рдард╡рд▓реЗрд▓реА key рдХрд╛рдвреВрди рдЯрд╛рдХрддреЗ, рдЖрдгрд┐ рджреЛрди IAM files рдиреБрдХрд╕рд╛рдирд╛рдЪрд╛ рдкрд░рд┐рдШ рдПрдХрд╛ repository рдЖрдгрд┐ рдПрдХрд╛ cluster рдкреБрд░рддрд╛ рд▓рд╣рд╛рди рдХрд░рддрд╛рдд. рд╣реЗ AWS рд╢рд╛рд│реЗрдЪреЗ "robots рд╕рд╛рдареА рдмреЕрдЬ" рдЖрд╣реЗ, EC2 instance рдРрд╡рдЬреА GitHub runner рд╕рд╣, рдЖрдгрд┐ рддреНрдпрд╛рдЪрд╛ IAM рд╕реНрд╡рдЪреНрдЫрддреЗрдЪрд╛ рдзрдбрд╛ pipeline рд▓рд╛ рд▓рд╛рд╡рд▓реЗрд▓рд╛.

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

Run рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ token рд╕рд╛рдареА least privilege. ci.yml рдлрдХреНрдд contents: read рджреЗрддреЗ; ship.yml рдмреЕрдЬрд▓рд╛ рд▓рд╛рдЧрдгрд╛рд░реА рдПрдХ рдЧреЛрд╖реНрдЯ рдЬреЛрдбрддреЗ:

permissions:
  contents: read
  id-token: write           # lesson 06: lets this run ask for an OIDC badge тАФ no stored AWS keys

рдмреЕрдЬрдЪреА step. push job рдлрдХреНрдд variable рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдЕрд╕реЗрд▓ рддреЗрд╡реНрд╣рд╛рдЪ рдЪрд╛рд▓рддреЛ (if: vars.AWS_ROLE_ARN != '' тАФ AWS рдирд╕рд▓реЗрд▓рд╛ fork рддреЛ рд╡рдЧрд│рддреЛ рдЖрдгрд┐ рд╣рд┐рд░рд╡рд╛рдЪ рд░рд╛рд╣рддреЛ). рдкрд╛рдЪ рдУрд│реА, рдЪрд╛рд░ рдкрд╛рдпрд▒реНрдпрд╛:

      - name: ЁЯкк show the badge, get a day pass (OIDC тЖТ temporary credentials)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
  1. id-token: write рдиреЗ рдкрд░рд╡рд╛рдирдЧреА рдорд┐рд│рд╛рд▓реНрдпрд╛рд╡рд░, action GitHub рдХрдбреЗ token рдорд╛рдЧрддреЗ; GitHub aud = sts.amazonaws.com рдЖрдгрд┐ sub = repo:BaluRaut/learn-cicd-school:ref:refs/heads/main рдЕрд╕рд▓реЗрд▓реНрдпрд╛ token рд╡рд░ рд╕рд╣реА рдХрд░рддреЗ.
  2. Action рддреЛ token рдЖрдгрд┐ role ARN рдШреЗрдКрди STS AssumeRoleWithWebIdentity call рдХрд░рддреЗ.
  3. STS GitHub рдЪреА рд╕рд╣реА рддрдкрд╛рд╕рддреЗ, рдордЧ role рдЪреА trust policy рддрдкрд╛рд╕рддреЗ: aud рдмрд░реЛрдмрд░ рдЖрд╣реЗ рдХрд╛, sub рдЬреБрд│рддреЛ рдХрд╛?
  4. STS temporary credentials рдкрд░рдд рджреЗрддреЗ (key, secret, session token тАФ default рдиреБрд╕рд╛рд░ рдПрдХ рддрд╛рд╕), рдЬреА action рдлрдХреНрдд рдпрд╛ job рд╕рд╛рдареА environment variables рдореНрд╣рдгреВрди export рдХрд░рддреЗ. Job рд╕рдВрдкрд▓рд╛, credentials рдЧреЗрд▓реА.

рдХреЛрдг тАФ trust policy, iam/github-oidc-trust-policy.json:

      "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
        "StringLike":   { "token.actions.githubusercontent.com:sub": "repo:BaluRaut/learn-cicd-school:ref:refs/heads/main" }
      }

sub рдЪреА рдУрд│ рд╣рд╛рдЪ рд╕рдВрдкреВрд░реНрдг рдореБрджреНрджрд╛ рдЖрд╣реЗ: рджреБрд╕рд▒реНрдпрд╛ repository рдордзрд▓рд╛, рдХрд┐рдВрд╡рд╛ рдпрд╛рдЪ repo рдЪреНрдпрд╛ feature branch рд╡рд░рдЪрд╛ run рд╡реЗрдЧрд│рд╛ sub рджрд╛рдЦрд╡рддреЛ рдЖрдгрд┐ рдирд╛рдХрд╛рд░рд▓рд╛ рдЬрд╛рддреЛ. рддреБрдордЪреНрдпрд╛ fork рдЪреЗ runs repo:<your-user>/learn-cicd-school:тАж рдШреЗрдКрди рдпреЗрддрд╛рдд, рдореНрд╣рдгреВрди рддреБрдордЪреНрдпрд╛ fork рд╕рд╛рдареАрдЪреНрдпрд╛ role рдордзреНрдпреЗ рддрд┐рдереЗ рддреБрдордЪреЗ рдирд╛рд╡ рд╣рд╡реЗ.

рдХрд╛рдп тАФ permissions policy, iam/ci-role-policy.json (рдордзрд▓реНрдпрд╛ statement рдордзрд▓реНрдпрд╛ рд╕рд╛рдд ecr: actions рд╡рдЧрд│рд▓реНрдпрд╛ рдЖрд╣реЗрдд):

    { "Sid": "EcrLogin", "Effect": "Allow", "Action": "ecr:GetAuthorizationToken", "Resource": "*" },
    {
      "Sid": "EcrPushOneRepo",
      тАж
      "Resource": "arn:aws:ecr:ap-south-1:123456789012:repository/hello-courier"
    },
    { "Sid": "FindTheCluster", "Effect": "Allow", "Action": "eks:DescribeCluster",
      "Resource": "arn:aws:eks:ap-south-1:123456789012:cluster/school-eks" }

GetAuthorizationToken рдореНрд╣рдгрдЬреЗ registry login, рдЖрдгрд┐ рддреЛ рд░рдЪрдиреЗрдиреБрд╕рд╛рд░рдЪ рд╕рдВрдкреВрд░реНрдг account рд╕рд╛рдареА рдЕрд╕рддреЛ, рдореНрд╣рдгреВрди рддреНрдпрд╛рд▓рд╛ * рд▓рд╛рдЧрддреЛ. рд╡рдЧрд│рд▓реЗрд▓реНрдпрд╛ actions layers upload рдХрд░рддрд╛рдд рдЖрдгрд┐ image рдареЗрд╡рддрд╛рдд тАФ repositories delete рдХрд┐рдВрд╡рд╛ create рдХрд░рдгрд╛рд░реЗ рдХрд╛рд╣реАрд╣реА рдирд╛рд╣реА тАФ рдЖрдгрд┐ рдЙрд░рд▓реЗрд▓реА рдкреНрд░рддреНрдпреЗрдХ рдУрд│ рдПрдХрдЪ repository рдХрд┐рдВрд╡рд╛ рдПрдХрдЪ cluster рдирд╛рд╡рд╛рдиреЗ рд╕рд╛рдВрдЧрддреЗ. ecr:* рдирд╛рд╣реА, eks:* рдирд╛рд╣реА, iam:* рдирд╛рд╣реА.

ЁЯФБ рд╣реЗрдЪ CircleCI рдордзреНрдпреЗ
      - test:                            # NO context: tests never see cloud credentials
      - build-and-push:
          context: school-cicd
          requires: [test]

Context рдореНрд╣рдгрдЬреЗ organization settings рдордзрд▓рд╛ environment variables рдЪрд╛ рдирд╛рд╡ рджрд┐рд▓реЗрд▓рд╛ рд╕рдВрдЪ; рдЬреЗ jobs рддреЛ рдпрд╛рджреАрдд рд▓рд┐рд╣рд┐рддрд╛рдд рддреНрдпрд╛рдВрдирд╛рдЪ рддреЛ рдорд┐рд│рддреЛ. build-and-push рдордзреНрдпреЗ, role_arn: ${AWS_ROLE_ARN} рд╕рд╣ aws-cli/setup CircleCI рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ OIDC token рдиреЗ рддреАрдЪ рдмреЕрдЬрдЪреА рдЕрджрд▓рд╛рдмрджрд▓ рдХрд░рддреЗ.

ЁЯжК рд╣реЗрдЪ GitLab CI рдордзреНрдпреЗ
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.com

id_tokens: рдореБрд│реЗ GitLab рдмреЕрдЬ $GITLAB_OIDC_TOKEN рдордзреНрдпреЗ рдмрдирд╡рддреЗ; рдордЧ .aws-badge anchor рдЪреЗ before_script рд╣рд╛рддрд╛рдиреЗ aws sts assume-role-with-web-identity рдЪрд╛рд▓рд╡рддреЗ рдЖрдгрд┐ рддреАрди credentials export рдХрд░рддреЗ. <<: *aws-badge merge рдХрд░рдгрд╛рд▒реНрдпрд╛ jobs рдирд╛рдЪ рддреЗ рдорд┐рд│рддреЗ тАФ test job рд▓рд╛ рдирд╛рд╣реА.

ЁЯОй рд╣реЗрдЪ Jenkins рдордзреНрдпреЗ
        withAWS(credentials: 'aws-school-cicd', region: env.AWS_REGION) {

Jenkins credentials рд╕реНрд╡рддрдГрдЪреНрдпрд╛ store рдордзреНрдпреЗ рдареЗрд╡рддреЗ рдЖрдгрд┐ withAWS (Pipeline: AWS Steps plugin) рддреА рдлрдХреНрдд block рдЪреНрдпрд╛ рдЖрддрдЪ рдЙрдШрдб рдХрд░рддреЗ. Jenkins рд╕рд╛рдареА рдХреЛрдгрддреАрд╣реА hosted рд╕реЗрд╡рд╛ рдЖрдкреЛрдЖрдк рдмреЕрдЬрд╡рд░ рд╕рд╣реА рдХрд░рдд рдирд╛рд╣реА, рдореНрд╣рдгреВрди рдкреНрд░рд╛рдорд╛рдгрд┐рдХ setup рдореНрд╣рдгрдЬреЗ рдЖрдзреАрдЪ IAM role рдЕрд╕рд▓реЗрд▓реНрдпрд╛ EC2 instance рдХрд┐рдВрд╡рд╛ pod рд╡рд░рдЪрд╛ agent тАФ рдкреБрдиреНрд╣рд╛ AWS рд╢рд╛рд│реЗрдЪрд╛ рдзрдбрд╛ 05.

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

# 1) read the two lists тАФ no AWS needed
cat iam/github-oidc-trust-policy.json iam/ci-role-policy.json   # WHO: aud + sub ┬╖ WHAT: one repo, one cluster

# 2) what your fork (lesson 03) carries: no variables тЖТ push/deploy skipped; no secrets тЖТ no stored keys
gh variable list && gh secret list

# 3) OPTIONAL тАФ needs an AWS account (ECR storage and transfer may cost a little)
ACCOUNT=$(aws sts get-caller-identity --query Account --output text); ME=$(gh api user --jq .login)
aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com   # older AWS CLIs also insist on --thumbprint-list
# drop the "_comment" line (IAM accepts only known keys); put YOUR account id and GitHub user in
grep -v '"_comment"' iam/github-oidc-trust-policy.json | sed -e "s/123456789012/$ACCOUNT/" -e "s|repo:BaluRaut/|repo:$ME/|" > /tmp/trust.json
grep -v '"_comment"' iam/ci-role-policy.json | sed "s/123456789012/$ACCOUNT/" > /tmp/perms.json
aws iam create-role --role-name learn-cicd-school-ci --assume-role-policy-document file:///tmp/trust.json
aws iam put-role-policy --role-name learn-cicd-school-ci --policy-name ci --policy-document file:///tmp/perms.json
aws ecr create-repository --repository-name hello-courier --region ap-south-1 --image-scanning-configuration scanOnPush=true
gh variable set AWS_ROLE_ARN --body "arn:aws:iam::$ACCOUNT:role/learn-cicd-school-ci"
gh variable set AWS_REGION --body ap-south-1 && gh variable set ECR_REPOSITORY --body hello-courier
# leave EKS_CLUSTER unset until lesson 10: the next push to main runs "push to ECR"; deploy stays skipped

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

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

рдбреЗ рдкрд╛рд╕ рд╣рд╛рддрд╛рдд рдШреЗрдКрди courier рдлреЛрдЯреЛрдХреЙрдкреА рдорд╢реАрдирдХрдбреЗ рдЬрд╛рддреЛ: рдбрдмрд╛ build рдХрд░рд╛, рддреЛ рд╕реБрд░реВ рд╣реЛрддреЛ рд╣реЗ рд╕рд┐рджреНрдз рдХрд░рд╛, рддреНрдпрд╛рд╡рд░ commit рдЪрд╛ рдард╕рд╛ рд▓рд┐рд╣рд╛, рд▓реЙрдХрд░рдордзреНрдпреЗ рдареЗрд╡рд╛. ЁЯУж

git checkout lesson-07-build-push-image

ЁЯкк Lesson 06 тАФ Secrets & least privilege: the courier's badge

ЁЯУН You are here: Lesson 06 of 12 ┬╖ Previous: lesson-05-artifacts-reports ┬╖ Next: lesson-07-build-push-image


ЁЯУж What's in this branch

Lessons 01тАУ05, plus the rule that keeps the mailroom safe тАФ the courier does not carry the school's master key. Real files:

ЁЯзТ Explain like I'm 5

The courier robot ЁЯУо has to get into the locker room ЁЯПж to file the photocopies. The lazy way: give it the master key ЁЯФС тАФ it opens the whole building, has no expiry date, and if the robot drops it in the corridor, whoever finds it owns the school. In CI that key is a stored AWS access key.

The school way: the robot wears a badge ЁЯкк saying "courier of THIS school, on THIS route". The guard checks it against a guest list тАФ signed by the school office? meant for this door? this route? тАФ and hands over a day pass ЁЯОл that opens one locker and stops working in about an hour. That is OIDC: GitHub signs the badge, AWS STS is the guard, the trust policy is the guest list, the permissions policy is the list of doors the pass opens.

Two more things in the courier's pocket. Variables are notes pinned on the notice board тАФ a region, a repository name тАФ anyone may read them. Secrets are sealed envelopes the mailroom blacks out in the logs it prints. But the marker only recognizes the exact text; photocopy the letter sideways (base64 it, split it, write it to a file) and the marker misses it.

ЁЯЧ║я╕П Diagram

flowchart LR
    job["ЁЯУо push job<br/>permissions: id-token: write"]
    gh["ЁЯПл GitHub OIDC<br/>signs a short-lived token"]
    sts["ЁЯЫВ AWS STS<br/>checks the trust policy (WHO)<br/>aud + sub"]
    ecr["ЁЯПж ECR hello-courier<br/>ci-role-policy (WHAT): one repo, one cluster"]
    job -->|"1 ask for a badge"| gh
    gh -->|"2 signed ID token"| job
    job -->|"3 show the badge"| sts
    sts -->|"4 day pass: temporary credentials, this job only"| ecr

тЭУ What

ЁЯза Master key vs badge

ЁЯФС stored access keys ЁЯкк badge тЖТ ЁЯОл day pass (OIDC)
where it lives in a secret, until someone rotates it nowhere тАФ minted per job
if it leaks valid until noticed and rotated expires within the hour; useless outside the trust policy
what it opens whatever the IAM user has only what the role's policy lists

ЁЯдФ Why

Leaked CI credentials are a classic incident: a key printed in a log, copied into an artifact, or read by a workflow from a fork. A stored key also has to be rotated, and in practice rarely is. OIDC removes the stored key, and the two IAM files shrink the blast radius to one repository and one cluster. It is the AWS school's "badges for robots" with a GitHub runner instead of an EC2 instance, plus its IAM hygiene lesson applied to a pipeline.

ЁЯФз How (in this repo)

Least privilege for the run's own token. ci.yml grants only contents: read; ship.yml adds the one thing the badge needs:

permissions:
  contents: read
  id-token: write           # lesson 06: lets this run ask for an OIDC badge тАФ no stored AWS keys

The badge step. The push job runs only when the variable exists (if: vars.AWS_ROLE_ARN != '' тАФ a fork without AWS skips it and stays green). Five lines, four steps:

      - name: ЁЯкк show the badge, get a day pass (OIDC тЖТ temporary credentials)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
  1. Allowed by id-token: write, the action asks GitHub for a token; GitHub signs one with aud = sts.amazonaws.com and sub = repo:BaluRaut/learn-cicd-school:ref:refs/heads/main.
  2. The action calls STS AssumeRoleWithWebIdentity with that token and the role ARN.
  3. STS verifies GitHub's signature, then checks the role's trust policy: does aud equal, does sub match?
  4. STS returns temporary credentials (key, secret, session token тАФ one hour by default), which the action exports as environment variables for this job only. Job over, credentials gone.

WHO тАФ the trust policy, iam/github-oidc-trust-policy.json:

      "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
        "StringLike":   { "token.actions.githubusercontent.com:sub": "repo:BaluRaut/learn-cicd-school:ref:refs/heads/main" }
      }

The sub line is the whole point: a run from another repository, or from a feature branch of this one, presents a different sub and is refused. Your fork's runs carry repo:<your-user>/learn-cicd-school:тАж, so a role for your fork needs your name there.

WHAT тАФ the permissions policy, iam/ci-role-policy.json (the middle statement's seven ecr: actions elided):

    { "Sid": "EcrLogin", "Effect": "Allow", "Action": "ecr:GetAuthorizationToken", "Resource": "*" },
    {
      "Sid": "EcrPushOneRepo",
      тАж
      "Resource": "arn:aws:ecr:ap-south-1:123456789012:repository/hello-courier"
    },
    { "Sid": "FindTheCluster", "Effect": "Allow", "Action": "eks:DescribeCluster",
      "Resource": "arn:aws:eks:ap-south-1:123456789012:cluster/school-eks" }

GetAuthorizationToken is the registry login and is account-wide by design, so it needs *. The elided actions upload layers and put an image тАФ nothing that deletes or creates repositories тАФ and each remaining line names one repository or one cluster. No ecr:*, no eks:*, no iam:*.

ЁЯФБ The same thing in CircleCI
      - test:                            # NO context: tests never see cloud credentials
      - build-and-push:
          context: school-cicd
          requires: [test]

A context is a named bundle of environment variables in the organization settings; only the jobs that list it receive it. Inside build-and-push, aws-cli/setup with role_arn: ${AWS_ROLE_ARN} does the same badge exchange with CircleCI's own OIDC token.

ЁЯжК The same thing in GitLab CI
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.com

id_tokens: makes GitLab mint the badge into $GITLAB_OIDC_TOKEN; the .aws-badge anchor's before_script then runs aws sts assume-role-with-web-identity by hand and exports the three credentials. Only jobs that merge in <<: *aws-badge get it тАФ the test job does not.

ЁЯОй The same thing in Jenkins
        withAWS(credentials: 'aws-school-cicd', region: env.AWS_REGION) {

Jenkins keeps credentials in its own store and withAWS (Pipeline: AWS Steps plugin) exposes them only inside the block. Nothing hosted signs a badge for Jenkins out of the box, so the honest setup is an agent on an EC2 instance or pod that already holds an IAM role тАФ AWS school lesson 05 again.

ЁЯзк Try it

# 1) read the two lists тАФ no AWS needed
cat iam/github-oidc-trust-policy.json iam/ci-role-policy.json   # WHO: aud + sub ┬╖ WHAT: one repo, one cluster

# 2) what your fork (lesson 03) carries: no variables тЖТ push/deploy skipped; no secrets тЖТ no stored keys
gh variable list && gh secret list

# 3) OPTIONAL тАФ needs an AWS account (ECR storage and transfer may cost a little)
ACCOUNT=$(aws sts get-caller-identity --query Account --output text); ME=$(gh api user --jq .login)
aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com   # older AWS CLIs also insist on --thumbprint-list
# drop the "_comment" line (IAM accepts only known keys); put YOUR account id and GitHub user in
grep -v '"_comment"' iam/github-oidc-trust-policy.json | sed -e "s/123456789012/$ACCOUNT/" -e "s|repo:BaluRaut/|repo:$ME/|" > /tmp/trust.json
grep -v '"_comment"' iam/ci-role-policy.json | sed "s/123456789012/$ACCOUNT/" > /tmp/perms.json
aws iam create-role --role-name learn-cicd-school-ci --assume-role-policy-document file:///tmp/trust.json
aws iam put-role-policy --role-name learn-cicd-school-ci --policy-name ci --policy-document file:///tmp/perms.json
aws ecr create-repository --repository-name hello-courier --region ap-south-1 --image-scanning-configuration scanOnPush=true
gh variable set AWS_ROLE_ARN --body "arn:aws:iam::$ACCOUNT:role/learn-cicd-school-ci"
gh variable set AWS_REGION --body ap-south-1 && gh variable set ECR_REPOSITORY --body hello-courier
# leave EKS_CLUSTER unset until lesson 10: the next push to main runs "push to ECR"; deploy stays skipped

тЪая╕П Common mistakes

тПня╕П Next

Day pass in hand, the courier walks to the photocopier: build the box, prove it starts, write the commit's fingerprint on it, file it in the locker. ЁЯУж

git checkout lesson-07-build-push-image
тЖР Previousartifacts reportsNext тЖТbuild push image

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