Use policy expressions with named values in Azure API management

When deploying your Azure API management via ARM templates you want to avoid putting environment depending variables in your template files. But injecting all settings via a parameter file is sometimes easier said than done. In my case, I needed to set an API policy to verify a certificate thumbprint. And that simple thing took me a few hours.

Linking the policy file

In your ARM templates you can refer to an API policy via a rawxml-link like:

{
      "apiVersion":"2019-01-01",
      "name":"[concat(parameters('apimServiceName'),'/', variables('apiName') ,'/policy')]",
      "type":"Microsoft.ApiManagement/service/apis/policies",
      "properties":{
        "value":"[concat(parameters('repoBaseUrl'), '/apis/api-demo/v1/api-demo.policy.xml',parameters('sasToken'))]",
        "format":"rawxml-link" 
      },
      "dependsOn": [
        "[resourceId('Microsoft.ApiManagement/service/apis',parameters('apimServiceName'),variables('apiName'))]"
      ]
    }

The policy file

To verify a certificate thumbprint in your policy file you can simply add:

<policies>
    <inbound>
        <choose>
            <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify() ||context.Request.Certificate.Thumbprint != "DESIRED-THUMBPRINT-IN-UPPER-CASE"))">
                <return-response>
                    <set-status code="403" reason="Invalid client certificate" />
                </return-response>
            </when>
        </choose>
        <base />
        <set-backend-service base-url="" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

In the policy file you’ll notice that the set-backend-service base-url is using a named value parameter. Likewise I tried to use a named value in the policy expression:

original named value

<when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify() ||context.Request.Certificate.Thumbprint != ""))">

Once you try to save that policy via the portal you get the error:

One or more fields contain incorrect values: Error in element ‘choose’ on line 3, column 10: ) expected

The microsoft docs explain how it actually works:

When this policy is evaluated, is replaced with its value: @(DateTime.Now.ToString()). Since the value is a policy expression, the expression is evaluated and the policy proceeds with its execution.

That means that we can simply define our named value with quotes and remove them from the policy:

named value with quotes

<when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify() ||context.Request.Certificate.Thumbprint != ))">

With that change your policy will evaluate correctly and work as expected.

What about multiple thumbprints?

As policy expressions are actually C# code (at least a subset of it) you can use LINQ. So now you can change the named value to a list:

named value containing a list

and change your policy file to:

<policies>
    <inbound>
        <choose>
            <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify() || !(new List<string>}.Any(c => c == context.Request.Certificate.Thumbprint)))">
                <return-response>
                    <set-status code="403" reason="Invalid client certificate" />
                </return-response>
            </when>
        </choose>
        <base />
        <set-backend-service base-url="" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Conclusion

It took me a while to find out, but the combination of policy expressions and named values gives you a lot of flexibility to define the exact policies you want.

References