top of page
Writer's pictureAndrew Kelleher

How to implement a file size upload policy in Azure API Management with Bicep

Updated: Sep 7, 2023

I’ve recently been building an application hosting platform for a customer. Multiple Azure services are being leveraged, but notably, we’re using Azure API Management to expose the backend containers’ endpoints.


One of the requirements was that the application’s users be restricted from uploading files larger than 10MB. There is already a check within the application code, but as a fallback, we also wanted to implement this restriction at the infrastructure level.


For this, I created an inbound API policy on API Management. This post dives into what such a policy looks like and how to deploy it using Bicep.


API Management Policies Overview

As a quick introduction, here’s an overview of API Management policies -

  • Policies are used to customize and control the behaviour of APIs by enforcing security, modifying request/response payloads, implementing rate limiting etc.

  • Policies are written using an XML-based policy language

  • API Management provides built-in policies that cover authentication, transformation, caching and error handling

  • Custom policies can also be created to meet specific requirements

Policy XML file

In our scenario, we want to create an inbound policy to limit the body size of POST operations. The screenshot below shows where the policy is defined within the Azure Portal. This is the default empty policy; it’s possible to edit this manually by clicking on Add policy or </>.

This is the XML for the file upload policy that we wish to add -


<policies>
    <inbound>
        <base />
        <choose>
            <when condition="@(context.Request.Method == "POST")">
                <choose>
                    <when condition="@(context.Request.Body.As<string>(preserveContent: true).Length<10485760)">
                        <!-- Payload is less than 10MB - do nothing and pass request -->
                    </when>
                    <otherwise>
                        <return-response>
                            <set-status code="413" reason="Payload Too Large" />
                            <set-body>@{
                                    return "Maximum allowed size for POST requests is 10,485,760 bytes (10 MB). This request has a size of "+ context.Request.Body.As<string>(preserveContent: true).Length +" bytes";
                            }</set-body>
                        </return-response>
                    </otherwise>
                </choose>
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <set-header name="ErrorSource" exists-action="override">
            <value>@(context.LastError.Source)</value>
        </set-header>
        <set-header name="ErrorReason" exists-action="override">
            <value>@(context.LastError.Reason)</value>
        </set-header>
        <set-header name="ErrorMessage" exists-action="override">
            <value>@(context.LastError.Message)</value>
        </set-header>
        <set-header name="ErrorScope" exists-action="override">
            <value>@(context.LastError.Scope)</value>
        </set-header>
        <set-header name="ErrorSection" exists-action="override">
            <value>@(context.LastError.Section)</value>
        </set-header>
        <set-header name="ErrorPath" exists-action="override">
            <value>@(context.LastError.Path)</value>
        </set-header>
        <set-header name="ErrorPolicyId" exists-action="override">
            <value>@(context.LastError.PolicyId)</value>
        </set-header>
        <set-header name="ErrorStatusCode" exists-action="override">
            <value>@(context.Response.StatusCode.ToString())</value>
        </set-header>
        <base />
    </on-error>
</policies>

You can copy/paste this as needed for your own APIs. I added the <on-error> statements to provide more verbose error logs; feel free to remove these if they’re not required.


Bicep code

As I’m deploying the Azure services via Bicep, I also needed a way to include the policy in the deployment.


The policy is stored as an XML file in the same folder as the Bicep definitions -


Within Bicep, we use the Microsoft.ApiManagement/service/apis/policies resource type to load the policy from the XML file and apply it to the parent API definition —

// API definition
resource api 'Microsoft.ApiManagement/service/apis@2022-09-01-preview' = {
  parent: apimanagement // parent api management resource
  name: 'myapi'
  properties: {
    authenticationSettings: {
      oAuth2AuthenticationSettings: []
      openidAuthenticationSettings: []
    }
    description: 'example api'
    displayName: 'myapi'
    path: 'myapi'
    protocols: [
      'https'
    ]
    serviceUrl: 'https://mybackendservice'
    subscriptionRequired: false
    subscriptionKeyParameterNames: {
      header: 'Ocp-Apim-Subscription-Key'
      query: 'subscription-key'
    }
  }
}

// Import API policy
resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2022-09-01-preview' = {
  parent: api // parent api resource
  name: 'custominboundpolicy'
  properties: {
    value: loadTextContent('apiPolicy.xml') // load api policy from xml file
    format: 'rawxml'
  }
}

Once the Bicep has been deployed, we can see the updated policy in the Azure Portal -


That’s it! Once I’d figured out the required policy, the rest was relatively simple. It’s worth pointing out that I had issues setting the format as xml and had to use rawxml instead.

88 views0 comments

Comments


bottom of page