Let Github Action Access Azure Resources without password
Tags: github, azure, cloud, devops
Published: 2023-01-03 11:11:13

พอกันที่กับการ rotate secret ของ Service Principal ที่ใช้กับ Github Action ทุก 3 เดือน 6 เดือน 
พอกันทีกับโอกาสที่ secret ของ Service Principal จะ leak

วันนี้มีสิ่งที่เรียกว่า **"Workload Identity Federation"** นำเสนอครับ

## Workload Identity Federation 
คือการที่เราทำการสร้าง trusted relationship กันระหว่าง Microsoft Identity Platform กับ External Identity Provider (IdP) เช่น GitHub พอ trust กันแล้ว เราจะสามารถใช้ token ของ External IdP ไปแลกเป็น access token ของ Azure เพื่อ access resources ใน Azure ที่ถูก protect ด้วย Azure AD ได้

### External Identity Provider (IdP) ที่ support ตอนนี้ เช่น

- GitHub Actions
- Google Cloud
- Workload ใน Kubernetes
- External Provider อื่นๆ ที่ support OpenID Connect (OIDC)

### มาดู flow ดีกว่าว่ามันทำงานยังไง

![Workload Identity Federation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j2ds8v59cf3092avq6jy.png)

1. External Workload เช่น github action ทำการขอ token จาก IdP ของตัวเอง
2. External IdP ทำการ generate แล้วให้ token กับ External Workload
3. External Workload ใช้ token ที่ได้มาเพื่อขอ access token จาก Microsoft identity platform
4. Microsoft identity platform ทำการตรวจสอบว่ามี trust relationship สร้างไว้อยู่หรือไม่ และ เอา token ที่ได้ไป validate กับ External IdP
5. ถ้าทุกอย่างเรียบร้อย Microsoft identity platform จะสร้าง access token แล้วส่งให้กับ External Workload
6. จากนั้น External Workload จะใช้ access token ที่ได้มาเช้าถึง resources ใน Azure โดย access token จะมี permission เท่ากับ service principal ที่ สร้าง trust relationship ไว้

## มาลองลงมือทำดีกว่า

สิ่งที่เราจะทำคือสร้าง 
- 1 Github Repos ที่มี 2 Actions และ 2 environments ( develop / production )
- 2 services principals ( develop / production )
- trust relationship ระหว่าง Service Principals และ Environment ของ Github
- 2 resource groups และให้ services principals เป็น Contributors ใน resource groups ( develop / production ) 
- สร้าง Github Action Workflow เพื่อทดสอบ storage account ใน resource group ตาม environemnt

### สร้าง Github Repos และ environment

- สร้าง Github Action Repo

![Create Github Action Repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xlvr3nb4xraxkedec9t.png)

![Create Github Action Repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hnfjg2489lrfd00okz1i.png)

- Create Environment ( ชื่อ environemnt จะถูกใช้ในการ สร้าง trust relationship ใน Azure Portal )
  - develop
  - production

![Create Environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bc1vasemx7g52lj49zi0.png)

![Environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w07ustvmfmbummfkdw29.png)

### สร้าง service principal

- ใน `Azure Portal` > `Azure Active Directory` > `App registrations` > `+ New Registration`

![App registrations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tlsmc6fq7hvkre9j281.png)

- ใส่ชื่อของ service principal เลือก single tenant แล้วกด `Register`
  - `sp-demogithub-az-asse-dev-001` สำหรับ develop
  - `sp-demogithub-az-asse-prd-001` สำหรับ production

![New Registration Dev](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ml2y5e0kdo5nfweb302x.png)

### สร้าง Trust Relationship ( Federated credentials )

- Click ที่ Service Principal ที่เราสร้างขึ้น > `Certificates & secrets` > `Federated credentials` > `+ Add credential`

![Federated credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkvgapz72z0987ninc1o.png)

- ใส่ข้อมูลของ Github Repo ที่ต้องการสร้าง Trust Relationship ด้วย
  - `Organization`: ใส่ชื่อ organization ของ ตัวเอง
  - `Repository`: ชื่อ repo ของ github ที่เราสร้างไว้ก่อนหน้า
  - `Entity type`: เลือก `Environment` ค่าอื่นดูต่อที่ [Entity type examples](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#entity-type-examples)
  - `Name`: ตั้งชื่อขอ Federated credentials นี้ ( FQDN friendly )

![Trust Relationship](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufo5wt1fvevvq52fp53d.png)

- กดที่ overview ของ Service Pricipal แล้วเก็บค่าเหล่านี้ไว้ใช้ add ใน github environment
  - `AZURE_CLIENT_ID`: Application (client) ID
  - `AZURE_TENANT_ID`: Directory (tenant) ID
  - `AZURE_SUBSCRIPTION_ID`: subscription ID ที่จำให้ Github Action Provision storage account

![Develop Environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9j776xisq0y84amjbma.png)

- ทำแบบเดียวกันกับ Service Principal ของ Production Environment

![Production Environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d45vlevx3h33m7fauyto.png)

### สร้าง resource group แล้ว assign Contributor role ให้กับ Service Principal

- สร้าง resource groups
  - `rg-demogithubfed-az-asse-dev-001` สำหรับ develop
  - `rg-demogithubfed-az-asse-prd-001` สำหรับ production

![resource groups](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovlelm71acyh66eun6qq.png)

- Create Role Assignment ให้ 
  - `sp-demogithub-az-asse-dev-001` เป็น Contributor ใน `rg-demogithubfed-az-asse-dev-001`

![sp-demogithub-az-asse-dev-001](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nt2qn1iwyjr2zizhcjbz.png)
  - `sp-demogithub-az-asse-prd-001` เป็น Contributor ใน `rg-demogithubfed-az-asse-prd-001`

![sp-demogithub-az-asse-prd-001](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5db1ugdzyopvu50qhfh.png)

## สร้าง Github workflow เพื่อทดสอบสร้าง storage account

- สร้าง Enviroment secret ใน Github
  - `AZURE_CLIENT_ID`: Application (client) ID
  - `AZURE_TENANT_ID`: Directory (tenant) ID
  - `AZURE_SUBSCRIPTION_ID`: subscription ID ที่จำให้ Github Action Provision storage account

![Enviroment secret 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81v0lofj52h6eqp6gi6b.png)

![Enviroment secret 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxl6a2ln1emvrgpys8vx.png)

![Enviroment secret 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t2d9uufc9249gm36q3a9.png)

> ทำแบบเดียวกัน กับ production enviroment

- สร้าง Github Workflow สำหรับ create github action

> Action ที่ใช้ login คือ [Azure Login](https://github.com/marketplace/actions/azure-login) โดยเราจะทำตามตัวอย่างนี้ [Sample workflow that uses Azure login action using OIDC to run az cli (Linux)](https://github.com/marketplace/actions/azure-login#sample-workflow-that-uses-azure-login-action-using-oidc-to-run-az-cli-linux)

![github create file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o23ep3yb1618va93g02.png)

- สร้าง file สำหรับ develop => `.github/workflows/dev-demo.yml`
    
``` bash
name: DEV - Demo Azure Login with OIDC
on:
   workflow_dispatch:

permissions:
  id-token: write
  contents: read
  
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: develop
    steps:
      - name: 'Az CLI login'
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
  
      - name: 'Run az commands'
        run: |
          az account show --output table
          az storage account create \
          --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} \
          --location southeastasia \
          --resource-group rg-githubfed-az-asse-dev-001 \
          --name stdemofeddev001 \
          --sku Standard_LRS \
          --default-action Deny \
          --min-tls-version TLS1_2 \
          --output table
``` 

- สร้าง file สำหรับ production => `.github/workflows/prd-demo.yml`

``` bash
name: PRD - Demo Azure Login with OIDC
on:
   workflow_dispatch:

permissions:
  id-token: write
  contents: read
  
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: 'Az CLI login'
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
  
      - name: 'Run az commands'
        run: |
          az account show --output table
          az storage account create \
          --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} \
          --location southeastasia \
          --resource-group rg-githubfed-az-asse-prd-001 \
          --name stdemofedprd001 \
          --sku Standard_LRS \
          --default-action Deny \
          --min-tls-version TLS1_2 \
          --output table
```

- ทดสอบ run Github Action

![Github action run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79ppzoi4iqf4nss07y9k.png)

![Github action result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2tfc3uwb2xpfhjzgitqi.png)

- ตรวจสอบ storage account ที่ Azure Portal

![storage account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f1s1oon7nzyuqjfcxyzt.png)

---

## References

- [Workload identity federation](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation)
- [Use the Azure login action with OpenID Connect](https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Cwindows#use-the-azure-login-action-with-openid-connect)
- [Configure an app to trust an external identity provider](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp)
- [Github Action | Azure Login](https://github.com/marketplace/actions/azure-login)