CI/CD
在部屬中心可以選擇 Github Actions,這樣就可以自動化部屬。
OpenIddict 憑證問題
OpenIddict 需要使用憑證來簽署 JWT,但 Azure App Service 預設不會讀取專案內的憑證。
目前已知三種方式解決這個問題:
將憑證放在 Azure Key Vault:未研究
將憑證放在 Azure App Service 的憑證中
將憑證放在專案中,並在 Azure App Service 中指定憑證
範例
這邊透過修改原本範本處理憑證的程式碼,來支援 Azure App Service 透過指紋來讀取憑證。
原本 AuthServerModule.cs 中的 PreConfigureServices 方法
serverBuilder.AddProductionEncryptionAndSigningCertificate(
   "openiddict.pfx", "password");
建立新擴充方法
public static class OpenIddictServerBuilderExtensions
{
    public static OpenIddictServerBuilder 
         AddProductionEncryptionAndSigningCertificate(
              this OpenIddictServerBuilder builder,
              string fileName,
              string passPhrase,
              string? certThumbprint)
    {
        var certificate = 
            LoadCertificate(fileName, passPhrase, certThumbprint);
        if (certificate == null)
        {
            throw new FileNotFoundException(
               $"Signing Certificate couldn't be found: {fileName} or with thumbprint {certThumbprint}");
        }
        builder.AddSigningCertificate(certificate);
        builder.AddEncryptionCertificate(certificate);
        return builder;
    }
    private static X509Certificate2? LoadCertificate(
         string fileName, string passPhrase, string? certThumbprint)
    {
        if (!string.IsNullOrEmpty(certThumbprint))
        {
            var certificate = 
               GetCertificateByThumbprint(certThumbprint);
            if (certificate != null)
            {
                return certificate;
            }
        }
        return File.Exists(fileName) ? 
            new X509Certificate2(fileName, passPhrase) : null;
    }
    private static X509Certificate2? GetCertificateByThumbprint(
         string certThumbprint)
    {
        using var certStore = 
            new X509Store(StoreName.My, StoreLocation.CurrentUser);
        certStore.Open(OpenFlags.ReadOnly);
        var certCollection = certStore.Certificates.Find(
            X509FindType.FindByThumbprint,
            certThumbprint,
            validOnly: false);
        return Enumerable.OfType<X509Certificate2>(certCollection)
            .FirstOrDefault();
    }
}
修改 AuthServerModule.cs 中的 PreConfigureServices 方法
serverBuilder.AddProductionEncryptionAndSigningCertificate(
   "openiddict.pfx", "password",
   configuration["OpenIddict:Thumbprint"]);
其中 Thumbprint 可以透過本地執行產生的 openiddict.pfx 上傳到 Azure App Service 憑證後取得指紋。
之後 Azure App Service 裡面就不需要有 openiddict.pfx 檔案,會透過指紋去找到正確的憑證。
這裡的問題是憑證過期之後,需要重新上傳憑證,並且更新指紋。
可能要改用第三種直接允許讀憑證檔案,然後從程式檢查憑證過期,自動重新產生新憑證。
configure-ssl-certificate-in-code
setting-up-abp-with-openiddict-on-azure-app-services
Github Actions
其中要注意修改 Github Actions 的設定檔,確保部屬時有正確的設定。
安裝 Volo.Abp.Studio.Cli
安裝 ABP libs
指定 PROJECT_PATH (因為一個方案內包含多個專案)
dotnet build & dotnet publish 指令 (指定 ${{ env.PROJECT_PATH }})
dotnet publish -o 指令須加上雙引號,不然會報錯
這個錯誤是由於命令行中包含了空格,導致 MSBuild 將 C:\Program Files\dotnet/myapp 路徑解析成多個參數。
MSBuild 無法理解帶有空格的路徑,除非將它們用引號括起來。
branch_project(dev).yml
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy ASP.Net Core app to Azure Web App - BookStore
on:
  push:
    branches:
      - cicd/dev/web
  workflow_dispatch:
jobs:
  build:
    runs-on: windows-latest
    env:
      PROJECT_PATH: 'src\BookStore.Web'  # 參數化的路徑
    steps:
      - uses: actions/checkout@v4
      - name: Set up .NET Core
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'
      - name: Install ABP CLI
        run: dotnet tool install -g Volo.Abp.Studio.Cli
      - name: Install ABP libs
        run: abp install-libs -wd ${{ env.PROJECT_PATH }}
      - name: Build with dotnet
        run: dotnet build ${{ env.PROJECT_PATH }} --configuration Release
      - name: dotnet publish
        run: dotnet publish ${{ env.PROJECT_PATH }} -c Release -o "${{env.DOTNET_ROOT}}/myapp"
      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v4
        with:
          name: .net-app
          path: ${{env.DOTNET_ROOT}}/myapp
  deploy:
    runs-on: windows-latest
    needs: build
    environment:
      name: 'dev'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    permissions:
      id-token: write #This is required for requesting the JWT
    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v4
        with:
          name: .net-app
      
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_XXX }}
          tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_XXX }}
          subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_XXX }}
      - name: Deploy to Azure Web App
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'BookStore'
          slot-name: 'dev'
          package: .
          
Auth server 快取版本
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy ASP.Net Core app to Azure Web App - BookStore
on:
  push:
    branches:
      - cicd/dev/auth
  workflow_dispatch:
jobs:
  build:
    runs-on: windows-latest
    env:
      PROJECT_PATH: 'src\BookStore.AuthServer'  # 參數化的路徑
    steps:
      - uses: actions/checkout@v4
      - name: Set up .NET Core
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'
          cache: true
          cache-dependency-path: '**/package-lock.json'
      - run: dotnet restore --locked-mode
      - name: Install ABP CLI
        run: dotnet tool install -g Volo.Abp.Studio.Cli
      - name: Install ABP libs
        run: abp install-libs -wd ${{ env.PROJECT_PATH }}
      - name: Build with dotnet
        run: dotnet build ${{ env.PROJECT_PATH }} --configuration Release
      - name: dotnet publish
        run: dotnet publish ${{ env.PROJECT_PATH }} -c Release -r win-x86 -o "${{env.DOTNET_ROOT}}/myapp"
      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v4
        with:
          name: .net-app
          path: ${{env.DOTNET_ROOT}}/myapp
  deploy:
    runs-on: windows-latest
    needs: build
    environment:
      name: 'dev'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    permissions:
      id-token: write #This is required for requesting the JWT
    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v4
        with:
          name: .net-app
      
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_XXX }}
          tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_XXX }}
          subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_XXX }}
      - name: Deploy to Azure Web App
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'BookStore'
          slot-name: 'dev'
          package: .
          
Host API
API 不需要 wwwroot libs,所以可以不用安裝 ABP libs。
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
name: Build and deploy ASP.Net Core app to Azure Web App - BookStore
on:
  push:
    branches:
      - cicd/dev/api
  workflow_dispatch:
jobs:
  build:
    runs-on: windows-latest
    env:
      PROJECT_PATH: 'src\BookStore.HttpApi.Host'  # 參數化的路徑
    steps:
      - uses: actions/checkout@v4
      - name: Set up .NET Core
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'
          cache: true
          cache-dependency-path: '**/package-lock.json'
      - run: dotnet restore --locked-mode
      - name: Build with dotnet
        run: dotnet build ${{ env.PROJECT_PATH }} --configuration Release
      - name: dotnet publish
        run: dotnet publish ${{ env.PROJECT_PATH }} -c Release -r win-x86 -o "${{env.DOTNET_ROOT}}/myapp"
      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v4
        with:
          name: .net-app
          path: ${{env.DOTNET_ROOT}}/myapp
  deploy:
    runs-on: windows-latest
    needs: build
    environment:
      name: 'dev'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    permissions:
      id-token: write #This is required for requesting the JWT
    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v4
        with:
          name: .net-app
      
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_XXX }}
          tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_XXX }}
          subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_XXX }}
      - name: Deploy to Azure Web App
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'BookStore'
          slot-name: 'dev'
          package: .
          
29 October 2024