Azure API Management with multiple backends - azure

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.

Related

URL Based routing on Azure

I am trying to do URL based routing for my APIs but I am not able to achieve as my API endpoints contain a wildcard in the middle of the URL, like:
/prodapi/v1.0/{appId}/level
/prodapi/v1.0/{appId}/products
appId is my customers white label ID, so it's unique for all our customers.
So far I have tried :
Azure application gateway [ But you can only handle at the end of your URL]
Azure Fron door [Have the same settings]
API management [ Not allowing me to do wildcard]
Can someone help me with any azure native or Out of the box solution.
If you want to route the traffic depending on a specific path parameter, you could use Api management service and write a inbound policy as such:
<policies>
<inbound>
<base />
<set-variable name="appId" value="#(context.Request.MatchedParameters["appId"])" />
<choose>
<when condition="#(int.Parse(context.Variables.GetValueOrDefault<string>("appId")) == 1)">
<set-backend-service base-url="https://google.com/" />
</when>
<when condition="#(int.Parse(context.Variables.GetValueOrDefault<string>("appId")) == 2)">
<set-backend-service base-url="https://twitter.com/" />
</when>
<otherwise>
<set-backend-service base-url="https://facebook.com/" />
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Replacing the backend-service with your prefer url ofc :)
API management is very powerful, so I'm sure that you'll find the solution there if this is not what you were looking for.

Can I call different backend on Azure API Management

I am trying to achieve that, when I call an APIM Endpoint based on the request headers It should different endpoint. for e.g when user call https://test.azure-api.net/testsvc-dev/api/test APIM should be able to send the request to https://testappv1:80/test or https://testappv2:80. right now I can see in the serviceURL, I can add only one. Is there any policy that I can use to do these kind of operations.
little more context: I am trying to access two different version of API on a single call. Instead of caller choosing which one to call,I am trying to make the APIM to decide based on the user.
You are probably looking for the set-backend-service policy.
Here an example from the Microsoft docs that changes the backend service based on a query parameter:
<policies>
<inbound>
<choose>
<when condition="#(context.Request.Url.Query.GetValueOrDefault("version") == "2013-05")">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
<when condition="#(context.Request.Url.Query.GetValueOrDefault("version") == "2014-03")">
<set-backend-service base-url="http://contoso.com/api/9.1/" />
</when>
</choose>
<base />
</inbound>
<outbound>
<base />
</outbound>
</policies>
Certainly, you could adopt the sample and query for the desired headers ;-)

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.

Is it possible to create a policy that will conditionally expose an api management endpoint in azure even when a opim-subscription key is required?

An example of what I am looking for is as follows but the allow-access element does not exist. What can I replace with so that the subscription key is not checked. i.e. in this case it would allow all callers access to the controller as long as they are making GET requests.
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.Method.Equals("GET"))">
<allow-access />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
Any help would be appreciated.
A workaround would be to turn off the Requires subscription setting on the product and check the subscription key in the inbound policy by yourself. Here is an example of how to do it.
Go to Settings of Starter product.
Uncheck Requires subscription and save.
Open the policies of the product and add the following policy to the inbound. The value of <check-header> policy is the subscription key of the Starter product.
<choose>
<when condition="#(!context.Request.Method.Equals("GET"))">
<check-header name="Ocp-Apim-Subscription-Key" failed-check-httpcode="401" failed-check-error-message="Not authorized" ignore-case="false">
<value>920b4e307f4f41ff9bd4a3bd6a5450ee</value>
</check-header>
</when>
</choose>

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