DEV Community

Kohei Kawata
Kohei Kawata

Posted on

Azure API Management authentication - Part.3

Summary

Following the article Part.2, I would like to share about how the sample code api-management-sample works. The secret management with Key Vault follow the concpet from Azure Pipelines secret management with Key Vault.

TOC

API deployment

In the deployment pipeline, it builds .NET API, generates the API documentation swagger.json, and deploys it to API Management. To generate swagger.json, the .NET project file needs to install Swashbuckle.AspNetCore.Cli package.

Project file example AzureAdAuth.csproj

<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
Enter fullscreen mode Exit fullscreen mode

In Program.cs, the API documentation should be defined

Program.cs

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new() { Title = "AzureAdAuthAPI", Version = "v1" });
});
Enter fullscreen mode Exit fullscreen mode

For a .NET API project, you can use .NET CLI local tool. The command below creates ./.config/dotnet-tools.json.

dotnet new tool-manifest
Enter fullscreen mode Exit fullscreen mode

Initially dotnet-tools.json file looks like this.

{
  "version": 1,
  "isRoot": true,
  "tools": {}
}
Enter fullscreen mode Exit fullscreen mode

Then you can run the command below to install Swashbuckle.AspNetCore.Cli. You have to match the version of it with the NugetPackage version defined in the proejct file above.

dotnet tool install Swashbuckle.AspNetCore.Cli --version 6.3.1
Enter fullscreen mode Exit fullscreen mode

After the command, dotnet-tools.json file looks like this.

{
  "version": 1,
  "isRoot": true,
  "tools": {
    "swashbuckle.aspnetcore.cli": {
      "version": "6.3.1",
      "commands": [
        "swagger"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you run the command below to generate swagger.json. v1 should match the version determined in Program.cs above.

dotnet swagger tofile --output swagger.json .\bin\Debug\net6.0\AzureAdAuth.dll v1
Enter fullscreen mode Exit fullscreen mode

The process of generating swagger.json above can be executed in the Azure Pipelines. In the Yaml file example below, swagger.json generated in the tasks is deployed in API Management.

api_pipeline.yml

- task: DotNetCoreCLI@2
  displayName: dotnet new tool-manifest
  inputs:
    command: custom
    custom: new
    arguments: tool-manifest
    workingDirectory: $(ApiDirectory)
- task: DotNetCoreCLI@2
  displayName: dotnet tool install
  inputs:
    command: custom
    custom: tool
    arguments: install Swashbuckle.AspNetCore.Cli --version $(SwashbuckleVersion)
    workingDirectory: $(ApiDirectory)
- task: DotNetCoreCLI@2
  displayName: dotnet swagger tofile
  inputs:
    command: custom
    custom: swagger
    arguments: tofile --output swagger.json $(DllPath) $(SwaggerVersion)
    workingDirectory: $(ApiDirectory)
- task: AzureCLI@2
  displayName: Deploy API to API Management
  inputs:
    azureSubscription: $(AZURE_SVC_NAME)
    scriptType: ps
    scriptLocation: inlineScript
    inlineScript: |
      az apim api import -g $(ResourceGroupName) `
        --service-name $(APIManagementName) `
        --api-id ${{AppServiceInstance.api}} `
        --path ${{AppServiceInstance.path}} `
        --specification-format OpenApiJson `
        --specification-path $(APIManagementSwaggerPath) `
        --service-url $(APIManagementServiceUrl)
Enter fullscreen mode Exit fullscreen mode

Subscription Key Validation vs Gateway Validation

The architecture of Subscription Key Validation and Gateway Validation is explained in the previous articles. In this section, I am going to explain what is the code difference between two architectures for the bicep and Yaml files.

Subscription Key Validation

Image description

Gateway Validation

Image description

Bicep

azuredeploy_subscription.bicep
azuredeploy_gateway.bicep

Subscription Key validation Gateway Validation
subscriptionRequired true false
AzureAdAuthAPI policy Get a token from Azure AD and remove subscription key from header Validate Azure AD token from client app
BasicAuthAPI policy Set username and password and remove subscription key from header Validate basic auth username and password
CertificateAuthAPI policy Get a certificate from Key Vault and remove subscription key from header Validate a client certificate and get a certificate from Key Vault

Azure Piplines Yaml

The only difference is which bicep file the pipeline execute, azuredeploy_subscription.bicep or azuredeploy_gateway.bicep

iac_pipeline_subscription.yml
iac_pipeline_gateway.yml

Integration test

The CI/CD pipeline api_pipeline.yml includes six types of integration test, Subscription Key Valition for three APIs and Gateway Validation for three APIs. All of integration tests are executing every time, and if the architecture is the Subscription Key Validation, three tests are succeeded and other three are failed. For the Gateway Validation architecture, it is vice versa.

Subscription key

Subscription key is stored in the Key Vault during the IaC pipeline deployment. The pipeline downloads the subscription key from the Key Vault and attaches to the header and then send requests to the API.

- task: AzurePowerShell@5
  continueOnError: true
  displayName: Integration test - Subscription key - ${{AppServiceInstance.name}}
  inputs:
    azureSubscription: $(AZURE_SVC_NAME)
    azurePowerShellVersion: latestVersion
    ScriptType: InlineScript
    Inline: |
      $subscriptionKey = Get-AzKeyVaultSecret -VaultName $env:KeyVaultName -Name $env:KVSECRET_NAME_SUBSCRIPTION_KEY -AsPlainText
      $uri = "https://$env:APIManagementName.azure-api.net/${{AppServiceInstance.name}}/Weatherforecast/RequireAuth"
      $headers = @{
        "Ocp-Apim-Subscription-Key" = $subscriptionKey
      }
      Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
Enter fullscreen mode Exit fullscreen mode

Azure AD token

  • All of necessary parameters such as application ID and secrests are from Pipeline Library and Key vault.
  • Use OAuth2.0 Client Credential flow because this is service authentication.
- task: AzurePowerShell@5
  continueOnError: true
  displayName: Integration test - Gateway validation - AzureAdAuth
  inputs:
    azureSubscription: $(AZURE_SVC_NAME)
    azurePowerShellVersion: latestVersion
    ScriptType: InlineScript
    Inline: |
      $clientSecret = Get-AzKeyVaultSecret -VaultName $env:KeyVaultName -Name $env:KVSECRET_NAME_CLIENTSECRET -AsPlainText
      $authorizeUri = "https://login.microsoftonline.com/$env:AAD_TENANTID/oauth2/v2.0/token"
      $body = 'grant_type=client_credentials' + `
      '&client_id=$(AAD_APPID_CLIENT)' + `
      '&client_secret=' + $clientSecret + `
      '&scope=api://$(AAD_APPID_BACKEND)/.default'
      $token = (Invoke-RestMethod -Method Post -Uri $authorizeUri -Body $body).access_token
      $Uri = "https://$env:APIManagementName.azure-api.net/AzureAdAuth/Weatherforecast/RequireAuth"
      $headers = @{
        "Authorization" = 'Bearer ' + $token
      }
      Invoke-RestMethod -Uri $Uri -Method Get -Headers $headers
Enter fullscreen mode Exit fullscreen mode

Basic auth

  • Password is downloaded from the Key Vault.
  • The username and password are converted to Base64 encoding.
- task: AzurePowerShell@5
  continueOnError: true
  displayName: Integration test - Gateway validation - BasicAuth
  inputs:
    azureSubscription: $(AZURE_SVC_NAME)
    azurePowerShellVersion: latestVersion
    ScriptType: InlineScript
    Inline: |
      $basicAuthUsername = $env:BASIC_AUTH_USER
      $basicAuthSecret = Get-AzKeyVaultSecret -VaultName $env:KeyVaultName -Name $env:KVSECRET_NAME_BASIC_PASS -AsPlainText
      $bytes = [System.Text.Encoding]::ASCII.GetBytes($basicAuthUsername + ':' + $basicAuthSecret)
      $authHeader = [Convert]::ToBase64String($bytes)
      $Uri = "https://$env:APIManagementName.azure-api.net/BasicAuth/Weatherforecast/RequireAuth"
      $headers = @{
        "Authorization" = 'Basic ' + $authHeader
      }
      Invoke-RestMethod -Uri $Uri -Method Get -Headers $headers
Enter fullscreen mode Exit fullscreen mode

Certificate auth

  • Download the certificate from the Key Vault with PFX file format.
  • The downloaded PFX file is attached to the request.
- task: AzurePowerShell@5
  continueOnError: true
  displayName: Integration test - Gateway validation - CertificateAuth
  inputs:
    azureSubscription: $(AZURE_SVC_NAME)
    azurePowerShellVersion: latestVersion
    ScriptType: InlineScript
    Inline: |
      $cert = Get-AzKeyVaultCertificate -VaultName $env:KeyVaultName -Name $env:KVCERT_NAME_API
      $secret = Get-AzKeyVaultSecret -VaultName $env:KeyVaultName -Name $cert.Name
      $secretValueText = '';
      $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret.SecretValue)
      try {
          $secretValueText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)
      } finally {
          [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)
      }
      $secretByte = [Convert]::FromBase64String($secretValueText)
      $x509Cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
      $x509Cert.Import($secretByte, "", "Exportable,PersistKeySet")
      $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx
      $pfxFileByte = $x509Cert.Export($type, $password)
      [System.IO.File]::WriteAllBytes("KeyVault.pfx", $pfxFileByte)
      $parameters = @{
          Method  = "GET"
          Uri     = "https://$env:APIManagementName.azure-api.net/CertificateAuth/Weatherforecast/RequireAuth"
          Certificate  = (Get-PfxCertificate "./KeyVault.pfx")
      }
      Invoke-RestMethod @parameters
Enter fullscreen mode Exit fullscreen mode

Top comments (0)