Azure API Management: Adding multiple Backend policies not allowed - azure

I'm trying to add multiple Backend policies to my Azure APIM Management endpoint, but I'm getting an error that I can only have one policy: "Error in element 'backend' on line xx, column 6: backend section allows only one policy to be specified"
<backend>
<forward-request />
<set-header name="Content-Type" exists-action="append">
<value>application/json</value>
</set-header>
<set-header name="x-correlation-id" exists-action="append">
<value>asdf-qwer-1234-zxcv</value>
</set-header>
</backend>
Is it possible to set multiple backend policies? If so, how can I do this? If not, what are some possible workarounds?

Thank you Vova and DeepDave-MT for the quick responses! Those are similar issues, but not necessarily related to multiple backend policies, rather related to having multiple backends.
We figured it out! A co-worker recommended moving the policies to the Inbound policies. This is rather interesting and wouldn't have guessed that.
Here's an example of what did the trick:
<policies>
<inbound>
...
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="x-correlation-id" exists-action="override">
<value>asdf-qwer-1234-zxcv</value>
</set-header>
</inbound>
<backend>
<forward-request />
</backend>
<outbound>
...
</outbound>
<on-error></on-error>
</policies>

Related

Azure Blob List "Value for one of the query parameters specified in the request URI is invalid" when using API Management service

I'm using API Management service to make a simple interface between a c# desktop application and a Azure Blob storage account.
API Management service is providing the authentication via a Subscription Key. This works fine for downloading blobs.
I also want to list the contents of an individual container / folder (prefix) using the same method.
I've tested the following HTTP call outside the API Management service, it works correctly.
https://<account>.blob.core.windows.net/files?restype=directory&comp=list&prefix=globalpackages/1.0
When I use this in API Management service, I'm returned an error:
<Error>
<Code>InvalidQueryParameterValue</Code>
<Message>Value for one of the query parameters specified in the request URI is invalid.
RequestId:917b0aa8-101e-006b-0709-8e85b0000000
Time:2022-07-02T11:47:29.0834806Z</Message>
<QueryParameterName>comp</QueryParameterName>
<QueryParameterValue>list</QueryParameterValue>
<Reason />
</Error>
My processing rules:
<policies>
<inbound>
<base />
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="Sec-Fetch-Site" exists-action="delete" />
<set-header name="Sec-Fetch-Mode" exists-action="delete" />
<set-header name="Sec-Fetch-Dest" exists-action="delete" />
<set-header name="Accept" exists-action="delete" />
<set-header name="Accept-Encoding" exists-action="delete" />
<set-header name="Referer" exists-action="delete" />
<set-header name="X-Forwarded-For" exists-action="delete" />
<set-header name="x-ms-version" exists-action="override">
<value>#{string version = "2017-11-09"; return version;}</value>
</set-header>
<set-backend-service base-url="#{
return String.Format("https://<storageaccount>.blob.core.windows.net/files?restype=directory&comp=list&prefix=globalpackages/1.0");
}" />
<authentication-managed-identity resource="https://storage.azure.com/" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
what am I doing wrong?
Based on the documentation available here, the query parameter should be restype=container instead of restype=directory if you need to list blobs inside a blob container.
Once you make this change, you should be able to list the blobs.

Azure API Management Conditional outbound headers

I'm using API Management to set outbound headers which was sufficient till now with a single domain using this policy block
<outbound>
<base />
<set-header name="Access-Control-Allow-Origin" exists-action="override">
<value>https://example.com</value>
</set-header>
</outbound>
However I now need to manage multiple domains for Access-Control-Allow-Origin and based on an approved list of domains. I have tried to use a conditional block but this does not work.
<outbound>
<base />
<choose>
<when condition="#(context.Request.OriginalUrl.Host == "example.com")">
<return-response>
<set-header name="Access-Control-Allow-Origin" exists-action="override">
<value>https://example.com</value>
</set-header>
</return-response>
</when>
<when condition="#(context.Request.OriginalUrl.Host == "example1.com")">
<return-response>
<set-header name="Access-Control-Allow-Origin" exists-action="override">
<value>https://example1.com</value>
</set-header>
</return-response>
</when>
<otherwise />
</choose>
</outbound>
We have an increasing list of domains which need to be handled to send a response back to the client with the header if domain from the request matches the list. Is there a better way to handle this and how can I make this work?
The CORS Policy allows listing multiple allowed origins and takes care of the response headers based on the request.

Azure APIM - Policy - Cannot get valid context - Error 500 - Object Not Set to Instance of Object

Referring to this page: https://learn.microsoft.com/en-us/azure/api-management/policies/send-request-context-info-to-backend-service
I am trying to get the User.Group using a policy such that I can restrict access to CRUD operations based on membership of 'read-only' or 'full-access' group.
All of the Microsoft Docs state that you can get user context by accessing #(context.User) and then whatever property you want. My proposed policy was this:
<policies>
<inbound>
<rewrite-uri template="/views/foo" />
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<choose>
<when condition="#(context.User.Groups.Select(g => g.Name).Contains("Read-Only-Group"))">
<return-response>
<set-status code="403" reason="Unauthorized" />
<set-body>You do not have write access - this operation is not available</set-body>
</return-response>
</when>
</choose>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The problem I am having is that whichever basic examples I try and implement in policy, I am getting "Error 500 - Object not set to an instance of object" whenever I try and get anything to do with the User. I understand this means my User object is not populated, but I don't get why.
Even if I refer to the basic examples on the Microsoft page to set some headers containing the Product name and User ID, they fail with the same error. Those pages don't contain any info on additional steps needed to access the User object.
How can I access these items?
I used https://reqbin.com/echo/get/json as a mock backend and this API operation policy:
<policies>
<inbound>
<rewrite-uri template="/echo/get/json" />
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<choose>
<when condition="#(context.User.Groups.Select(g => g.Name).Contains("Developers"))">
<return-response>
<set-status code="403" reason="Unauthorized" />
<set-body>You do not have write access - this operation is not available</set-body>
</return-response>
</when>
</choose>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I set the API to subscription required.
When running an operation with a subscription key of a product valid for the API, I get the correct response (as everybody is in group Developers by default):
You do not have write access - this operation is not available
Then unchecking subscription required and running the operation without a subscription key gives me:
{
"statusCode": 500,
"message": "Internal server error",
"activityId": "46600c4a-960a-479e-a634-8e2857cea512"
}
Hence, when you want to access context.User object, you need to work with an API which has subscription required.

Azure API Management with multiple backends

I have an Azure API Management set and running. The API calls are made to a Docker container running FastAPI on an Azure virtual machine. This backend container is responsible for running some AI models based on the queries it receives. It all works fine for a single user.
The thing is: these AI models are defined in a config file inside the container and are user-specific.
I want to use Azure API Management to route requests based on, say, the user subscription. In other words, given a subscription, I want to know which backend to call (each backend would be a container running a specific AI model for that particular user/company on an Azure virtual machine).
What is the best way to approach this?
I found out that you can use inbound policies to specify which backend to use depending on what group the user belongs to. The reference to my answer comes from here.
Go to your API Management, choose Groups and create groups that are relevant to your users. Then add your users to their respective groups.
Go to your API, select inbound rules and specify your backends using something like this:
<policies>
<inbound>
<choose>
<when condition="#(context.User.Groups.Select(g => g.Name).Contains("org1"))">
<set-backend-service base-url="https://abc-apim.azure-api.net/org1app" />
</when>
<when condition="#(context.User.Groups.Select(g => g.Name).Contains("org2"))">
<set-backend-service base-url="https://abc-apim.azure-api.net/org2app" />
</when>
<otherwise>
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Bearer error="Invalid user group"</value>
</set-header>
</return-response>
</otherwise>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I undestand this approach is not so elegant, since you have to hard code the backend routing in your policy. But it works.

Can Azure Api Management expose OpenAPI documentation?

We have some Azure Functions exposed through Api Management? Can Api Management expose a /swagger endpoint automatically, the same way the Swashbuckle package does for api's in Asp.Net.
Azure API management cannot automatically generate the swagger page. Azure API management only can provide you the API definition file. Then you can use other tools (such as Swagger UI) with the definition file to generate the page you need.
Besides, Azure API management has provided you the UI(https://youapimanagementname.portal.azure-api.net) to tell you how to use all the APIs.
You can expose your openapi documentation through the API itself.
The documentation of an API can be requested on
https://management.azure.com/subscriptions/[subscriptionid]/resourceGroups/[resourcegroupname]/Microsoft.ApiManagement/service/[servicename]/apis/[apiid]?export=true&format=openapi&api-version=2021-01-01-preview
Just create an additional operation (ex. openapi.yaml) on your API, call the url above through a custom policy and return the result. You can use the following policy
<policies>
<inbound>
<base />
<send-request mode="new" response-variable-name="result" timeout="300" ignore-error="false">
<set-url>#("https://management.azure.com/subscriptions/{{azure-subscriptionid}}/resourceGroups/{{azure-resourcegroup}}/providers/Microsoft.ApiManagement/service/" + context.Deployment.ServiceName + "/apis/" + context.Api.Id + "?export=true&format=openapi&api-version=2021-01-01-preview")</set-url>
<set-method>GET</set-method>
<authentication-managed-identity resource="https://management.azure.com/" />
</send-request>
<return-response>
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/yaml</value>
</set-header>
<set-body>#((string)(((IResponse)context.Variables["result"]).Body.As<JObject>()["value"]))</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
More info can be found on https://www.devprotocol.com/2021/07/20/expose-openapi-documentation-on-azure-api-management.html

Resources