Thuta Learning
CI/CD with GitHub Actions
IntermediateDevOps & Toolsbeginner

Secrets, Token များနှင့် Least Privilege

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Secrets, Token များနှင့် Least Privilege concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး pipeline ထဲမှာ job တွေ ဘယ်လိုစီးဆင်းပြီး ဘာက gate လုပ်သလဲ ခြေရာခံနိုင်ရန်
  • Workflow file ကို ဖတ်ပြီး ဘယ် job တွေ ဘယ်အစဉ်လိုက် run မလဲ ကြိုတင်ပြောနိုင်ရန်

နားလည်ထားရမယ့် အချက်

Secret ဆိုသည်မှာ GitHub က encrypt လုပ်၍ သိမ်းထားပြီး ခွင့်ပြုထားသော workflow run များထဲသို့သာ ထည့်ပေးသော တန်ဖိုးတစ်ခုဖြစ်သည်။ secrets.NAME ဖြင့် ရယူပြီး၊ ကျိုးကြောင်းဆီလျော်သော pattern မှာ workflow တစ်ခုလုံးသို့ ဖွင့်ပေးမည့်အစား လိုအပ်သော step တစ်ခုတည်းရဲ့ env: block ထဲသို့သာ map လုပ်ခြင်းဖြစ်သည်။ Repository secret များသည် အဲဒီ repo ထဲက workflow တိုင်းအတွက် ရရှိနိုင်ပြီး၊ environment secret များကတော့ အမည်ရှိသော environment တစ်ခုနှင့် တွဲထားကာ required-reviewer စည်းမျဉ်းနောက်တွင် ထားနိုင်သည် — ဒါက production credential များကို လူတစ်ယောက် အတည်ပြုမှသာ ထုတ်ပေးအောင် လုပ်နည်းဖြစ်သည်။

Masking ကို လူတွေ အလွန်အကျွံ ယုံကြည်တတ်သည်။ Actions သည် log output ထဲတွင် အဲဒီ run အတွက် ရရှိနိုင်သော secret များ၏ စာသားအတိအကျကို ရှာပြီး *** ဖြင့် အစားထိုးသည်။ ဒါက token တစ်ခုကို မှားပြီး echo လုပ်မိသည့် ထင်ရှားသော မတော်တဆမှုကို ကာကွယ်ပေးသည်။ သို့သော် တန်ဖိုးကို အသွင်ပြောင်းလိုက်လျှင် လိုက်၍ မမီတော့ပါ။ base64 လုပ်၊ URL-encode လုပ်၊ ဖြတ်ယူ၊ တစ်လုံးချင်း တစ်ကြောင်းစီ ရိုက်ထုတ်၊ သို့မဟုတ် ပြန် serialise လုပ်မည့် JSON ထဲ ထည့်လိုက်လျှင် masker သည် အဲဒီ string ကို မမှတ်မိတော့ပါ။ Masking ကို အမှားများအတွက် ကယ်ကွင်းအဖြစ်သာ သဘောထားပါ၊ နယ်နိမိတ်တစ်ခုအဖြစ် မဟုတ်ပါ။

GITHUB_TOKEN ကတော့ ကွဲပြားသည် — run တစ်ခုစီအတွက် အလိုအလျောက် ထုတ်ပေးပြီး run ပြီးလျှင် သက်တမ်းကုန်သွားသဖြင့် ဘယ်တော့မှ rotate လုပ်စရာ မလိုပါ။ ၎င်း၏ default scope သည် repository သို့မဟုတ် organisation setting မှလာပြီး၊ workflow တကယ်လိုအပ်သည်ထက် များစွာ ကျယ်ပြန့်နေတတ်သည်။ permissions: block သည် အဲဒါကို ပြန်သတ်မှတ်ပေးသည် — သင် မဖော်ပြထားသော အရာတိုင်း none ဖြစ်သွားပြီး၊ workflow level တွင်လည်းကောင်း job တစ်ခုချင်းစီတွင်လည်းကောင်း ရေးနိုင်သည်။

နောက်ဆုံးအနေဖြင့် fork မှ pull_request event သည် read-only token ဖြင့် run ပြီး secret လုံးဝ မရပါ။ ဒါက ဖြေရှင်းရမည့် bug မဟုတ်ပါ — မရှိလျှင် မည်သူမဆို သင့် deploy key ကို ရိုက်ထုတ်စေမည့် PR တစ်ခု ဖွင့်နိုင်မည် ဖြစ်သည်။

text
GITHUB_TOKEN SCOPE, WIDEST TO NARROWEST
---------------------------------------
  [1] repo or org default setting (read and write)
      contents:w  issues:w  pull-requests:w  packages:w  pages:w ...
      +---------------------------------------------------------+

  [2] workflow-level  permissions: contents: read
      contents:r      every other scope -> none
      +--------------+

  [3] job-level  permissions: contents: read, packages: write,
                              id-token: write
      contents:r packages:w id-token:w    all others -> none
      +------------------------------+

  [4] pull_request from a FORK
      contents:r only, and secrets.* resolve to empty strings
      +--------+

  narrower is safer: a step can only misuse what it was granted

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

Version tag တစ်ခုက ဖြစ်ပေါ်စေသော publish workflow တစ်ခုကို စဉ်းစားကြည့်ပါ။ လိုအပ်သည်မှာ စွမ်းရည်နှစ်ခုသာ — repository ကို ဖတ်ရန်နှင့် package တစ်ခု တင်ရန်။ ကျန်အားလုံး — issue ရေးခြင်း၊ pull request ပြင်ခြင်း၊ deployment ရွှေ့ခြင်း — သည် မလိုအပ်ဘဲ ရှိနေသော attack surface ဖြစ်သည်။

ဒါကြောင့် workflow သည် ထိပ်ဆုံးတွင် permissions: contents: read ကို ချထားပြီး၊ ဒါက file ထဲက job တိုင်းကို read-only default ဖြစ်စေသည်။ publish job ကမှ packages: write နှင့် id-token: write ကို ထပ်ဖြည့်သည်။ id-token သည် npm publish --provenance က OIDC မှတစ်ဆင့် လက်မှတ်ထိုးထားသော attestation ထုတ်နိုင်စေရန် လိုအပ်သည် — မပါလျှင် flag က ကျရှုံးမည်ဖြစ်ပြီး၊ အဲဒီ ကျရှုံးမှုသည် ရိုးသားသော ကျရှုံးမှုဖြစ်သည်၊ အကြောင်းက permission ကို တကယ်ပင် မပေးထားခဲ့သောကြောင့်ဖြစ်သည်။

environment: production သည် job ကို environment တစ်ခုနှင့် တွဲပေးသဖြင့် NPM_TOKEN secret ကို workflow တိုင်း ရယူနိုင်သော repository secret ထဲတွင် ထားမည့်အစား reviewer စည်းမျဉ်းနောက်တွင် ထားနိုင်သည်။

Publish မလုပ်ခင် token ရှိမရှိ စစ်သော step ရှိရခြင်းမှာ secrets context သည် step-level if: တွင် မရရှိနိုင်သောကြောင့်ဖြစ်သည်။ if: secrets.NPM_TOKEN != '' ဟု ရေး၍ မရပါ။ အလုပ်ဖြစ်သော pattern မှာ secret ကို env: သို့ map လုပ်ပြီး shell ထဲတွင် environment variable ကို စစ်ခြင်းဖြစ်သည် — ဒါက publish တစ်ဝက်တွင် ဖြစ်တတ်သော ရှုပ်ထွေးသည့် authentication error ကို job ထိပ်ဆုံးရှိ ဖတ်ရလွယ်သော message တစ်ခု ဖြစ်စေပြီး၊ fork PR တစ်ခုသည် empty string ရမည်ဖြစ်၍ ဒီနေရာမှာပင် ရပ်တန့်မည်ဖြစ်သည်။

အတူတူ စမ်းရေးကြည့်မယ်

yaml
name: Publish package

on:
  push:
    tags: ['v*']

permissions:
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: https://registry.npmjs.org

      - name: Fail early when no token is available
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: |
          if [ -z "$NPM_TOKEN" ]; then
            echo "NPM_TOKEN is empty: forked pull request, or the secret was never created."
            exit 1
          fi

      - name: Install dependencies
        run: npm ci

      - name: Publish with provenance
        run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
You should see
Workflow သည် v* နှင့် ကိုက်ညီသော tag တစ်ခု push လုပ်မှသာ run သဖြင့် pull request တစ်ခုက publish ကို လုံးဝ မဖြစ်ပေါ်စေနိုင်ပါ။ File ထဲက job တိုင်းသည် repository content ကို ဖတ်ရုံသာ ကန့်သတ်ထားသော token ဖြင့် စတင်သည်။ publish job ကမှ package ရေးခွင့်နှင့် OIDC id-token တောင်းခွင့်ကို ထပ်ဖြည့်ပြီး၊ ကျန်ဘာမှ မဖြည့်ပါ။ Job သည် environment တစ်ခုကို အမည်ဖော်ပြထားသဖြင့် အဲဒီ environment တွင် required reviewer ရှိလျှင် run သည် အတည်ပြုချက်ကို စောင့်ဆိုင်းပြီး၊ ထို့နောက်မှသာ environment ရဲ့ secret များ ရရှိလာသည်။ Presence check သည် publish မလုပ်ခင် run သည် — NPM_TOKEN သည် empty string ဖြစ်နေလျှင် (fork ဖြစ်သောကြောင့်ဖြစ်စေ၊ secret ကို ဖန်တီးမထားသောကြောင့်ဖြစ်စေ) publish တစ်ဝက်တွင် ကျရှုံးမည့်အစား ဖတ်ရလွယ်သော message ဖြင့် ချက်ချင်း ကျရှုံးသည်။ Token တန်ဖိုးသည် log တစ်ကြောင်းထဲတွင် စာသားအတိအကျ ပေါ်လာခဲ့လျှင် Actions က ကြယ်ပွင့်များဖြင့် အစားထိုးမည်၊ သို့သော် အဲဒီ ပုံစံအတိအကျအတွက်သာဖြစ်ပြီး၊ အသွင်ပြောင်းထားသော သို့မဟုတ် encode လုပ်ထားသော မိတ္တူတစ်ခုကတော့ အပြည့်အစုံ ပေါ်လာမည်ဖြစ်သည်။

၅ မိနစ် စမ်းကြည့်

permissions block မရှိသေးသော workflow တစ်ခုသို့ permissions: contents: read ကို ထည့်ပြီး၊ ဘယ် step များ ကျရှုံးသွားသလဲ ရှာပါ — အဲဒီ ကျရှုံးမှုများသည် workflow က တိတ်တဆိတ် မှီခိုနေခဲ့သော permission များ ဖြစ်သည်။ အဲဒါတွေကိုသာ job level တွင် ပြန်ပေးပါ။ ထို့နောက် စွန့်ပစ်ရန် private repository တစ်ခုတွင် secret တစ်ခုကို သိထားသော တန်ဖိုးဖြင့် သတ်မှတ်ပြီး၊ step တစ်ခုတွင် ၎င်း၏ base64 ကို ရိုက်ထုတ်ကြည့်ပါ။ Masking သည် အသွင်ပြောင်းမှုကို လိုက်၍ မမီကြောင်း ကိုယ်တိုင် အတည်ပြုပြီး၊ ပြီးလျှင် အဲဒီ repository ကို ဖျက်ပစ်ပါ။

သတိလေးတစ်ချက်

Step တစ်ခုတွင် if: secrets.MY_TOKEN != '' ဟု ရေးခြင်း — secrets context သည် step-level condition တွင် မရရှိနိုင်သဖြင့် secret ကို env: သို့ map လုပ်ပြီး shell ထဲတွင် environment variable ကို စစ်ရမည်။

Secret ကို အသွင်ပြောင်းပြီးနောက် masking ကို မှီခိုခြင်း — base64 encode၊ URL-encode သို့မဟုတ် ဖြတ်ယူထားသော token သည် masker မမှတ်မိသော string ဖြစ်သွားပြီး log ထဲတွင် အပြည့်အစုံ ပေါ်လာသည်။

GitHub Docs - Using secrets in GitHub ActionsCI/CD with GitHub Actions

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • Step တစ်ခုတွင် if: secrets.MY_TOKEN != '' ဟု ရေးခြင်း — secrets context သည် step-level condition တွင် မရရှိနိုင်သဖြင့် secret ကို env: သို့ map လုပ်ပြီး shell ထဲတွင် environment variable ကို စစ်ရမည်။
  • Secret ကို အသွင်ပြောင်းပြီးနောက် masking ကို မှီခိုခြင်း — base64 encode၊ URL-encode သို့မဟုတ် ဖြတ်ယူထားသော token သည် masker မမှတ်မိသော string ဖြစ်သွားပြီး log ထဲတွင် အပြည့်အစုံ ပေါ်လာသည်။
  • Workflow ကို production branch ပေါ် တိုက်ရိုက်မစမ်းဘဲ branch (သို့) test repository တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

permissions block မရှိသေးသော workflow တစ်ခုသို့ permissions: contents: read ကို ထည့်ပြီး၊ ဘယ် step များ ကျရှုံးသွားသလဲ ရှာပါ — အဲဒီ ကျရှုံးမှုများသည် workflow က တိတ်တဆိတ် မှီခိုနေခဲ့သော permission များ ဖြစ်သည်။ အဲဒါတွေကိုသာ job level တွင် ပြန်ပေးပါ။ ထို့နောက် စွန့်ပစ်ရန် private repository တစ်ခုတွင် secret တစ်ခုကို သိထားသော တန်ဖိုးဖြင့် သတ်မှတ်ပြီး၊ step တစ်ခုတွင် ၎င်း၏ base64 ကို ရိုက်ထုတ်ကြည့်ပါ။ Masking သည် အသွင်ပြောင်းမှုကို လိုက်၍ မမီကြောင်း ကိုယ်တိုင် အတည်ပြုပြီး၊ ပြီးလျှင် အဲဒီ repository ကို ဖျက်ပစ်ပါ။

You'll know it worked when: Workflow သည် v* နှင့် ကိုက်ညီသော tag တစ်ခု push လုပ်မှသာ run သဖြင့် pull request တစ်ခုက publish ကို လုံးဝ မဖြစ်ပေါ်စေနိုင်ပါ။ File ထဲက job တိုင်းသည် repository content ကို ဖတ်ရုံသာ ကန့်သတ်ထားသော token ဖြင့် စတင်သည်။ publish job ကမှ package ရေးခွင့်နှင့် OIDC id-token တောင်းခွင့်ကို ထပ်ဖြည့်ပြီး၊ ကျန်ဘာမှ မဖြည့်ပါ။ Job သည် environment တစ်ခုကို အမည်ဖော်ပြထားသဖြင့် အဲဒီ environment တွင် required reviewer ရှိလျှင် run သည် အတည်ပြုချက်ကို စောင့်ဆိုင်းပြီး၊ ထို့နောက်မှသာ environment ရဲ့ secret များ ရရှိလာသည်။ Presence check သည် publish မလုပ်ခင် run သည် — NPM_TOKEN သည် empty string ဖြစ်နေလျှင် (fork ဖြစ်သောကြောင့်ဖြစ်စေ၊ secret ကို ဖန်တီးမထားသောကြောင့်ဖြစ်စေ) publish တစ်ဝက်တွင် ကျရှုံးမည့်အစား ဖတ်ရလွယ်သော message ဖြင့် ချက်ချင်း ကျရှုံးသည်။ Token တန်ဖိုးသည် log တစ်ကြောင်းထဲတွင် စာသားအတိအကျ ပေါ်လာခဲ့လျှင် Actions က ကြယ်ပွင့်များဖြင့် အစားထိုးမည်၊ သို့သော် အဲဒီ ပုံစံအတိအကျအတွက်သာဖြစ်ပြီး၊ အသွင်ပြောင်းထားသော သို့မဟုတ် encode လုပ်ထားသော မိတ္တူတစ်ခုကတော့ အပြည့်အစုံ ပေါ်လာမည်ဖြစ်သည်။

Secrets, Token များနှင့် Least Privilege | Thuta Learning