GitHub Actions CI/CD cho .NET 2026 — OIDC, Egress Firewall và Immutable Actions cho Pipeline Production

Posted on: 4/17/2026 3:11:11 PM

Mỗi lần developer push code, một chuỗi sự kiện phức tạp cần xảy ra: build, test, phân tích tĩnh, đóng gói container, deploy lên staging, chạy smoke test, rồi mới promote lên production. Làm thủ công? Mất 45 phút và dễ sai. Tự động hoá với GitHub Actions? Dưới 8 phút và không bao giờ quên bước nào. Năm 2026, GitHub Actions không chỉ là CI/CD runner mà đã trở thành nền tảng DevOps toàn diện với hệ thống bảo mật cấp enterprise, parallel steps, OIDC authentication, và egress firewall — tất cả tích hợp native trong GitHub.

Bài viết này đi sâu vào kiến trúc CI/CD pipeline cho .NET 10 trên GitHub Actions: từ caching NuGet/Docker giảm 80% thời gian build, reusable workflows cho multi-repo, đến bảo mật supply chain với immutable actions và triển khai zero-credential lên Azure qua OIDC.

39% Giảm giá runner so với 2024
<8 phút Full pipeline .NET 10 (có cache)
0 Credentials lưu trữ (OIDC)
Layer 7 Egress Firewall trên runner

Kiến trúc CI/CD Pipeline cho .NET 10

Một pipeline .NET production-grade cần tách biệt rõ ràng CI (Continuous Integration — mỗi push) và CD (Continuous Deployment — chỉ merge vào main). Ranh giới này ngăn việc deploy nhầm và giữ feedback loop nhanh cho developer.

graph LR
    subgraph CI["CI — Mỗi Push/PR"]
        direction TB
        A["Checkout + Cache Restore"] --> B["dotnet restore"]
        B --> C["dotnet build"]
        C --> D["dotnet test"]
        D --> E["Code Analysis"]
    end
    subgraph CD["CD — Merge vào main"]
        direction TB
        F["Docker Build + Push"] --> G["Deploy Staging"]
        G --> H["Smoke Test"]
        H --> I["Deploy Production"]
    end
    CI --> CD
    style CI fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style CD fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
    style A fill:#e94560,stroke:#fff,color:#fff
    style I fill:#4CAF50,stroke:#fff,color:#fff

Hình 1: Tổng quan pipeline CI/CD tách biệt cho .NET 10

CI trên mọi push, CD chỉ trên main

Quy tắc vàng: CI chạy trên mỗi push và pull request để phát hiện lỗi sớm. CD chỉ trigger khi merge vào nhánh main — ngăn chặn triệt để việc deploy nhầm từ feature branch. Pattern này được GitHub khuyến nghị chính thức cho mọi dự án production.

Chiến lược Caching — Giảm 80% Thời gian Build

Cache là yếu tố quyết định tốc độ pipeline. Trên GitHub Actions, có hai loại cache quan trọng cho .NET: NuGet package cacheDocker BuildKit layer cache.

NuGet Package Caching

Không có cache, dotnet restore phải download lại toàn bộ NuGet packages mỗi lần chạy — thường mất 60–90 giây cho project vừa. Với actions/cache keyed trên hash các file .csproj, cache hit giảm restore xuống dưới 5 giây.

- name: Cache NuGet packages
  uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
    restore-keys: |
      nuget-${{ runner.os }}-

Cache key sử dụng hash của tất cả file .csproj — bất kỳ thay đổi nào trong dependencies đều invalidate cache đúng cách. restore-keys cho phép fallback về cache cũ nhất của cùng OS, vẫn tiết kiệm đáng kể so với cold download.

Docker BuildKit Layer Caching

Đối với pipeline đóng gói container, Docker BuildKit layer cache là game-changer. Sử dụng GitHub Actions cache backend (type=gha), build time giảm từ 4–5 phút xuống dưới 60 giây khi cache hit.

- name: Build and push Docker image
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ${{ steps.meta.outputs.tags }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Tuỳ chọn mode=max cache toàn bộ layers kể cả intermediate — tối ưu cho project nhiều stages trong Dockerfile. Kết hợp với docker/metadata-action để tự động generate tags: sha-abc1234 cho traceability và latest cho main branch.

Kỹ thuật CacheKhông CacheCó Cache (hit)Tiết kiệm
NuGet Restore60–90s<5s~93%
Docker Build240–300s30–60s~80%
dotnet build (incremental)45–60s10–15s~75%
Tổng pipeline~8–10 phút~2–3 phút~70%

Workflow YAML hoàn chỉnh cho .NET 10

Dưới đây là workflow production-ready kết hợp CI + CD, NuGet cache, Docker build, và deploy qua OIDC — không lưu bất kỳ credential nào dạng secret dài hạn.

name: CI/CD .NET 10

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

permissions:
  contents: read
  id-token: write     # Bắt buộc cho OIDC
  packages: write     # Push container lên GHCR

env:
  DOTNET_VERSION: '10.0.x'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      - name: Cache NuGet
        uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
          restore-keys: nuget-${{ runner.os }}-

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore -c Release

      - name: Test
        run: dotnet test --no-build -c Release --logger trx --results-directory results

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: results/*.trx

  deploy:
    needs: build-and-test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Login to Azure (OIDC)
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy to Azure Container Apps
        uses: azure/container-apps-deploy-action@v2
        with:
          containerAppName: my-app
          resourceGroup: my-rg
          imageToDeploy: ${{ steps.meta.outputs.tags }}

Về permission id-token: write

Permission id-token: write cho phép workflow yêu cầu OIDC token từ GitHub. Token này có thời hạn ngắn (thường 10 phút), chỉ hợp lệ cho đúng repository và workflow đang chạy. Azure Entra ID verify token này qua federated credential — không cần client secret hay certificate. Đây là cách duy nhất nên deploy lên cloud năm 2026.

OIDC — Deploy Zero-Credential lên Azure

Truyền thống, CI/CD pipeline cần lưu service principal password dạng GitHub Secret. Vấn đề: secret có thể bị leak qua log, fork, hoặc compromised action. OIDC (OpenID Connect) loại bỏ hoàn toàn long-lived credentials.

sequenceDiagram
    participant GH as GitHub Actions
    participant IDP as GitHub OIDC Provider
    participant AZ as Azure Entra ID
    participant RES as Azure Resources
    GH->>IDP: Yêu cầu OIDC token
    IDP-->>GH: JWT token (repo, branch, workflow)
    GH->>AZ: Trình diện JWT token
    AZ->>AZ: Verify issuer + subject claims
    AZ-->>GH: Access token (ngắn hạn)
    GH->>RES: Deploy với access token
    Note over AZ,RES: Không secret nào được lưu trữ

Hình 2: Luồng xác thực OIDC giữa GitHub Actions và Azure

Thiết lập OIDC trên Azure

Cấu hình phía Azure gồm 3 bước: tạo App Registration, thêm Federated Credential, và gán RBAC role.

# 1. Tạo App Registration
az ad app create --display-name "github-actions-deploy"
APP_ID=$(az ad app list --display-name "github-actions-deploy" --query "[0].appId" -o tsv)

# 2. Tạo Service Principal
az ad sp create --id $APP_ID

# 3. Thêm Federated Credential
az ad app federated-credential create --id $APP_ID --parameters '{
  "name": "github-main-branch",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:myorg/myrepo:ref:refs/heads/main",
  "audiences": ["api://AzureADTokenExchange"]
}'

# 4. Gán role cho resource group
az role assignment create \
  --assignee $APP_ID \
  --role "Contributor" \
  --scope "/subscriptions/{sub-id}/resourceGroups/my-rg"

Claim subject trong federated credential giới hạn chính xác repo và branch nào được phép deploy. Nghĩa là dù attacker fork repo, OIDC token từ fork sẽ có subject khác và bị Azure reject — bảo mật hơn static secret nhiều lần.

Custom Claims mới 2026

GitHub vừa bổ sung repository custom properties vào OIDC token claims. Cho phép tạo trust policy dựa trên metadata tuỳ chỉnh của repo — ví dụ chỉ cho deploy khi repo có property environment: production. Attribute-based access control (ABAC) này linh hoạt hơn nhiều so với per-repo subject matching.

Bảo mật Supply Chain — Security Roadmap 2026

Năm 2026, GitHub đầu tư mạnh vào bảo mật CI/CD với ba trụ cột: Immutable Actions, Egress Firewall, và Actions Data Stream. Đây là phản hồi trực tiếp từ các sự cố supply chain attack nghiêm trọng (như Trivy supply chain compromise tháng 3/2026).

Immutable Actions — Khoá chặt Dependencies

Vấn đề cốt lõi: action dependencies được resolve tại runtime qua mutable references (tags, branches). Tag v4 hôm nay có thể trỏ đến commit khác hôm mai nếu maintainer bị compromise. Điều này nghĩa là CI/CD pipeline không deterministic.

Giải pháp của GitHub: dependencies: section mới trong workflow YAML, lock toàn bộ direct và transitive dependencies bằng commit SHA. Mọi workflow chạy đúng code đã được review — hash mismatch sẽ halt execution trước khi job bắt đầu.

# Pattern hiện tại (KHÔNG an toàn)
- uses: actions/checkout@v4          # Tag có thể bị move

# Pattern an toàn (Pin SHA)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.7

# Pattern tương lai (dependencies lock)
dependencies:
  actions/checkout:
    version: v4.1.7
    sha: b4ffde65f46336ab88eb53be808477a3936bae11
    # Transitive deps cũng bị lock

Bài học từ Trivy Supply Chain Attack (03/2026)

Tháng 3/2026, action aquasecurity/trivy-action bị compromise — attacker inject malicious code vào tag mutable, ảnh hưởng hàng nghìn workflow. Pipeline nào pin SHA thì an toàn hoàn toàn. Pipeline dùng tag @master hoặc @latest thì bị khai thác. Đây là lý do buộc phải pin SHA cho mọi third-party action.

Egress Firewall — Kiểm soát kết nối ra ngoài

GitHub đang xây dựng native egress firewall hoạt động tại Layer 7, nằm bên ngoài runner VM. Dù attacker giành quyền root bên trong runner, firewall vẫn bất biến và không thể bypass.

graph TB
    subgraph Runner["GitHub-hosted Runner VM"]
        W["Workflow Step"] --> NET["Outbound Request"]
    end
    subgraph FW["Egress Firewall (Layer 7)"]
        NET --> CHECK["Kiểm tra Allowlist"]
        CHECK -->|Cho phép| EXT["External Service"]
        CHECK -->|Từ chối| BLOCK["Blocked + Logged"]
    end
    subgraph AUDIT["Audit Trail"]
        CHECK --> LOG["Request → Workflow → Job → Step"]
    end
    style FW fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style BLOCK fill:#ff9800,stroke:#fff,color:#fff
    style EXT fill:#4CAF50,stroke:#fff,color:#fff

Hình 3: Egress Firewall kiểm soát outbound traffic từ runner

Tổ chức có thể:

  • Monitor: Xem toàn bộ outbound traffic, tự động audit mỗi request với workflow/job/step tương ứng
  • Allowlist: Chỉ cho phép kết nối đến domains cần thiết (nuget.org, ghcr.io, azure endpoints)
  • Block: Chặn hoàn toàn kết nối không mong muốn — ngăn data exfiltration từ compromised step

Parallel Steps — Tính năng được yêu cầu nhiều nhất

Từ 2020, GitHub Community Discussion #14484 về parallel steps đã thu hút hàng nghìn upvotes. Năm 2026, GitHub chính thức phát triển tính năng này với mục tiêu ship trước giữa năm.

Hiện tại, các steps trong một job chạy tuần tự. Để song song hoá, phải tách thành nhiều jobs — phức tạp và tốn overhead do mỗi job cần runner riêng. Parallel steps cho phép chạy các bước độc lập đồng thời trong cùng một job.

graph LR
    subgraph Before["Hiện tại: Tuần tự"]
        direction TB
        S1["Restore (30s)"] --> S2["Build (45s)"]
        S2 --> S3["Unit Test (60s)"]
        S3 --> S4["Integration Test (90s)"]
        S4 --> S5["Lint (20s)"]
    end
    subgraph After["Parallel Steps"]
        direction TB
        P1["Restore + Build (50s)"] --> P2["Unit Test (60s)"]
        P1 --> P3["Integration Test (90s)"]
        P1 --> P4["Lint (20s)"]
    end
    style Before fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
    style After fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50

Hình 4: Parallel steps giảm tổng thời gian pipeline từ 245s xuống ~140s

Với parallel steps, pipeline .NET có thể chạy unit test, integration test, và linting đồng thời sau khi build xong — giảm thời gian tổng từ tổng các bước (tuần tự) xuống bước dài nhất (song song).

Reusable Workflows — DRY cho Multi-Repo

Khi tổ chức quản lý nhiều .NET microservices, duplicate workflow YAML là nightmare. Reusable Workflows cho phép định nghĩa pipeline một lần trong repo trung tâm, gọi lại từ mọi repo service.

# .github/workflows/dotnet-ci.yml (repo trung tâm: myorg/ci-templates)
name: .NET CI Template
on:
  workflow_call:
    inputs:
      dotnet-version:
        type: string
        default: '10.0.x'
      project-path:
        type: string
        required: true
    secrets:
      AZURE_CLIENT_ID:
        required: true
      AZURE_TENANT_ID:
        required: true

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ inputs.dotnet-version }}
      - run: dotnet restore ${{ inputs.project-path }}
      - run: dotnet build ${{ inputs.project-path }} --no-restore -c Release
      - run: dotnet test ${{ inputs.project-path }} --no-build -c Release
# .github/workflows/ci.yml (repo service)
name: CI
on: [push, pull_request]

jobs:
  ci:
    uses: myorg/ci-templates/.github/workflows/dotnet-ci.yml@main
    with:
      project-path: src/MyService.sln
    secrets:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}

Reusable Workflows + OIDC: Lưu ý quan trọng

Khi reusable workflow yêu cầu OIDC token, caller workflow phải khai báo permissions: id-token: write. OIDC subject claim sẽ chứa thông tin của caller repo (nơi gọi), không phải template repo. Điều này đảm bảo federated credential trên Azure vẫn match đúng repo đang deploy.

Matrix Builds — Test song song trên nhiều phiên bản

Matrix strategy cho phép chạy cùng một bộ test trên nhiều phiên bản .NET, OS, hoặc database đồng thời. Đặc biệt hữu ích cho thư viện NuGet cần hỗ trợ multi-target.

jobs:
  test:
    strategy:
      matrix:
        dotnet: ['8.0.x', '9.0.x', '10.0.x']
        os: [ubuntu-latest, windows-latest]
      fail-fast: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ matrix.dotnet }}
      - run: dotnet test --configuration Release

Với fail-fast: false, tất cả 6 combinations (3 .NET × 2 OS) chạy đến hoàn tất dù một job fail — giúp phát hiện toàn bộ lỗi compatibility trong một lần chạy thay vì phải fix từng cái.

Runner Pricing 2026 — Mạnh hơn, rẻ hơn

Tháng 1/2026, GitHub giảm giá runner đáng kể: runner 4-vCPU "Standard" mới có giá bằng runner 2-vCPU cũ năm 2024. Tức gấp đôi compute với cùng chi phí.

Runner TypevCPURAMGiá ($/phút)So với 2024
ubuntu-latest (cũ 2024)27 GB$0.008
Standard Runner 2026416 GB$0.008-39% per vCPU
Large Runner832 GB$0.016Giữ nguyên
GPU Runner (Linux)428 GB + T4$0.07Mới 2025

Free tier vẫn rất hào phóng: 2,000 phút/tháng cho public repo (không giới hạn) và repo private trên GitHub Free plan. Với caching tốt, đủ cho team nhỏ 3–5 developer chạy CI/CD hàng ngày.

Checklist Bảo mật cho CI/CD Production

Dựa trên GitHub Security Roadmap 2026 và bài học từ các sự cố supply chain, dưới đây là checklist bảo mật cần thiết cho mọi pipeline .NET.

Hạng mụcThực hànhMức độ
Pin ActionsPin mọi third-party action bằng full SHA, không dùng tagBắt buộc
OIDCDùng OIDC thay static credentials cho cloud deployBắt buộc
PermissionsKhai báo permissions tối thiểu, không dùng default write-allBắt buộc
Branch ProtectionRequire status checks + review trước merge vào mainBắt buộc
Egress FirewallBật monitor mode, xây dựng allowlist, sau đó enforceKhuyến nghị
DependabotTự động update action versions + NuGet packagesKhuyến nghị
CODEOWNERSBảo vệ .github/workflows/ bằng CODEOWNERSKhuyến nghị
Secret ScanningBật push protection để chặn commit chứa secretsBắt buộc

Roadmap GitHub Actions 2026

Q1 2026
Timezone support cho scheduled jobs — không còn phải tính UTC offset thủ công. Workflow dispatch trả về run ID để programmatic tracking.
Q1–Q2 2026
Actions Data Stream — visibility layer mới, audit log chi tiết cho mọi workflow execution. Egress Firewall native trên GitHub-hosted runners.
H1 2026
Parallel Steps — chạy các bước độc lập đồng thời trong cùng job. Immutable Actions với dependencies lock file.
Q2 2026
Case function trong expressions. UX improvements với faster page load. Azure private networking failover cho hosted runners.

Kết luận

GitHub Actions năm 2026 không chỉ là "CI/CD tool" mà đã trở thành security-first DevOps platform. Với egress firewall, immutable actions, và OIDC — lần đầu tiên pipeline CI/CD có thể đạt mức bảo mật ngang với production infrastructure. Kết hợp caching NuGet + Docker BuildKit giảm 80% thời gian build, reusable workflows chuẩn hoá pipeline cho toàn tổ chức, và parallel steps sắp tới sẽ giảm thêm 40% thời gian nữa.

Đối với team .NET 10, GitHub Actions là lựa chọn tự nhiên nhất: tích hợp sâu với GitHub, free tier hào phóng, runner mạnh hơn với giá rẻ hơn, và hệ sinh thái action đồ sộ. Quan trọng nhất — pin SHA cho mọi action, dùng OIDC cho mọi cloud deploy.

Bắt đầu ngay

Nếu đang dùng Azure DevOps Pipelines hoặc Jenkins, migration sang GitHub Actions không phức tạp như tưởng. GitHub cung cấp hướng dẫn migration chính thức cho từng CI system. Bước đầu tiên: tạo một workflow đơn giản chỉ chạy dotnet test trên mỗi PR — sau đó mở rộng dần.

Nguồn tham khảo: