Azure API Management has a very powerful concept of policies: logic that you can inject in the request or response pipeline of your APIs. These policies can be defined on four levels:
- All APIs: a global policy that is applicable for all your API calls
- Product: a policy that gets executed when the API is accessed via a subscription key linked to this product (optional)
- API: a policy that gets executed for all API methods on a specific API
- Operation: a policy that runs for a certain API operation only
The All APIs policy is very handy, because it gets executed for every API call. It is often used to secure all your APIs in a single policy, to configure central monitoring or to hide stack traces. However, did you know that it is not guaranteed that this policy will execute?
The problem
The effective policy that gets executed is typically a combination of those different levels. Within the XML policy, the <base/> element determines the order in which they get executed. The <base/> element refers to the content of the “parent” policy, which can contain on its turn another <base/> element. If, for some reason, somebody removes the <base/> element from the policy, the global policy is not executed. This might lead to a huge security issue.
The solution
I’ve found two possible solutions and they are preferably used together!
Validate XML policies within the build pipeline
In the build pipeline, you can execute the following PowerShell script against every policy XML file, to ensure that it contains the <base/> element in every section.
[CmdletBinding()] | |
param ( | |
$PolicyString | |
) | |
Write-Host $PolicyString | |
$PolicyXml = new-object System.Xml.XmlDocument | |
$PolicyXml.LoadXml($PolicyString) | |
if($PolicyXml.SelectNodes("//inbound/base").Count -eq 0) | |
{ | |
Write-Error "Missing base element in the inbound section" | |
} | |
if($PolicyXml.SelectNodes("//outbound/base").Count -eq 0) | |
{ | |
Write-Error "Missing base element in the outbound section" | |
} | |
if($PolicyXml.SelectNodes("//on-error/base").Count -eq 0) | |
{ | |
Write-Error "Missing base element in the on-error section" | |
} |
Validate your API Management instance
As an alternative, you can connect to your API Management instance itself and validate all policies on each level. The following script performs this task for you:
[CmdletBinding()] | |
param ( | |
$ApimName, | |
$ApimResourceGroup | |
) | |
function Is-ValidPolicy { | |
[CmdletBinding()] | |
param([string] $PolicyString) | |
if(![string]::IsNullOrEmpty($PolicyString)) | |
{ | |
$PolicyXml = new-object System.Xml.XmlDocument | |
$PolicyXml.LoadXml($PolicyString) | |
if($PolicyXml.SelectNodes("//inbound/base").Count -eq 0 -or $PolicyXml.SelectNodes("//outbound/base").Count -eq 0 -or $PolicyXml.SelectNodes("//on-error/base").Count -eq 0) | |
{ | |
return $false | |
} | |
} | |
return $true | |
} | |
$ApimContext = New-AzApiManagementContext -ResourceGroupName $ApimResourceGroup -ServiceName $ApimName | |
$Apis = Get-AzApiManagementApi -Context $ApimContext | |
foreach($Api in $Apis) | |
{ | |
Write-Host "Validating API '$($Api.Name)'" | |
$ApiPolicy = Get-AzApiManagementPolicy -Context $ApimContext -ApiId $Api.ApiId | |
if(!(Is-ValidPolicy -PolicyString $ApiPolicy)) | |
{ | |
Write-Error "Missing base element in the API policy of $($Api.Name)" | |
Write-Host $ApiPolicy | |
} | |
$Operations = Get-AzApiManagementOperation -Context $ApimContext -ApiId $Api.ApiId | |
foreach($Operation in $Operations) | |
{ | |
Write-Host "Validating Operation '$($Operation.Name)'" | |
$OperationPolicy = Get-AzApiManagementPolicy -Context $ApimContext -ApiId $Api.ApiId -OperationId $Operation.OperationId | |
if(!(Is-ValidPolicy -PolicyString $OperationPolicy)) | |
{ | |
Write-Error "Missing base element in the '$($Operation.Name)' operation policy of $($Api.Name)" | |
Write-Host $OperationPolicy | |
} | |
} | |
} | |
$Products = Get-AzApiManagementProduct -Context $ApimContext | |
foreach($Product in $Products) | |
{ | |
Write-Host "Validating Product '$($Product.Title)'" | |
$ProductPolicy = Get-AzApiManagementPolicy -Context $ApimContext -ProductId $Product.ProductId | |
if(!(Is-ValidPolicy -PolicyString $ProductPolicy)) | |
{ | |
Write-Error "Missing base element in the product policy of $($Product.Title)" | |
Write-Host $ProductPolicy | |
} | |
} |
Conclusion
Not many people are aware of the potentially dangerous impact of the <base/> element. That’s why it is important to enforce its presence, certainly when you are relying on a global policy that arranges your security centrally. I’ve raised a feature request to the product team, to introduce a setting that allows to enforce this behavior. I hope it will make it to the product!
Sharing is caring!
Toon