Azure API Management CSRF Policy - azure

I'm trying to create an inbound policy to pass my x-csrf-token to a Post method. However, when i check the tracing, i'm getting null value for the x-csrf-token.
I'm using the snippet given here in the https://github.com/Azure/api-management-policy-snippets/blob/master/examples/Get%20X-CSRF%20token%20from%20SAP%20gateway%20using%20send%20request.policy.xml.
Have anyone come across this issue before?
<!--
IMPORTANT:
- Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.
- To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.
- To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.
- To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar.
- To remove a policy, delete the corresponding policy statement from the policy document.
- Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope.
- Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope.
- Policies are applied in the order of their appearance, from the top down.
- Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope.
-->
<!-- The policy defined in this file shows how to implement X-CSRF pattern used by many APIs. The example is specific to SAP Gateway. -->
<!-- Detailed description of the scenario and solution can be found on: -->
<!-- https://github.com/MartinPankraz/AzureSAPODataReader. -->
<policies>
<inbound>
<base />
<authentication-basic username="{{SAP-test-user}}" password="{{SAP-test-PW}}" />
<set-variable name="checkRequestUrl" value="#(context.Request.Url.ToString())" />
<rewrite-uri template="/" />
<choose>
<!-- CSRF-token only required for every operation other than GET or HEAD -->
<when condition="#(context.Request.Method != "GET" && context.Request.Method != "HEAD")">
<!-- Creating a HEAD subrequest to save request overhead and get the SAP CSRF token and cookie.-->
<send-request mode="new" response-variable-name="SAPCSRFToken" timeout="10" ignore-error="false">
<set-url>#(context.Request.Url.ToString())</set-url>
<set-method>HEAD</set-method>
<set-header name="X-CSRF-Token" exists-action="override">
<value>Fetch</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization"))</value>
</set-header>
</send-request>
<!-- Extract the token and cookie from the "SAPCSRFToken" and set as header in the POST request. -->
<choose>
<when condition="#(((IResponse)context.Variables["SAPCSRFToken"]).StatusCode == 200)">
<set-header name="X-CSRF-Token" exists-action="override">
<value>#(((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("x-csrf-token"))</value>
</set-header>
<set-header name="Cookie" exists-action="override">
<value>#{
string rawcookie = ((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("Set-Cookie");
string[] cookies = rawcookie.Split(';');
/* new session sends a XSRF cookie */
string xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("sap-XSRF"));
/* existing sessions sends a SessionID. No other cases anticipated at this point. Please create a GitHub Pull-Request if you encounter uncovered settings. */
if(xsrftoken == null){
xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("SAP_SESSIONID"));
}
return xsrftoken.Split(',')[1];}</value>
</set-header>
</when>
</choose>
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

Related

How to share xml_content in Terraform API Management resource

We are creating our API management instance and associated endpoints through Terraform. All our API endpoints (close to a hundred) share the same policy logic for routing the request to an Azure function.
An example policy is like so -
resource "azurerm_api_management_api_operation_policy"
"api_put_policy" {
api_name = azurerm_api_management_api.my_api.name
resource_group_name = azurerm_resource_group.main.name
api_management_name = azurerm_api_management.my_api.name
operation_id = azurerm_api_management_api_operation.my_api.operation_id
xml_content = <<XML
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.Headers.GetValueOrDefault("Key") == "password")">
<set-backend-service base-url="${data.azurerm_function_app.MyFunctionApp.default_hostname}" />
</when>
<when condition="#(context.Request.Headers.GetValueOrDefault("Key") != null)">
<return-response>
<set-status code="400" reason="Bad Request" />
<set-body>An incorrect Key header has been passed in the request</set-body>
</return-response>
</when>
<otherwise>
<set-backend-service base-url="${other-route-variable}" />
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
So we have the same XML_content being used on every API endpoint, only the variables get set differently depending what function app is going to be routed to.
Is there a way this xml content could be moved into a file where parameters can be passed through to then generate the XML for every API policy so we have the logic stored in only one place?
I have looked at a variety of uses of the file() function but can't see anything that could be done to achieve what I need here.
Thanks
Yes, you can use the templatefile function for that [1]. The templatefile function works in the following way:
templatefile(path, vars)
Where the path represents the file location and the vars are a map of variables that will be used to replace the placeholders in the file itself. I will give an example based on the XML file you have. You would first create the template file inside of the same directory probably (e.g., xml_content.tpl):
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.Headers.GetValueOrDefault("Key") == ${password})">
<set-backend-service base-url="${hostname_url}" />
</when>
<when condition="#(context.Request.Headers.GetValueOrDefault("Key") != null)">
<return-response>
<set-status code="400" reason="Bad Request" />
<set-body>An incorrect Key header has been passed in the request</set-body>
</return-response>
</when>
<otherwise>
<set-backend-service base-url="${other-route-variable}" />
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
Notice that I removed the password value and the data source output in url. Those will now expect variables with names password and hostname_url to be provided when using the templatefile function:
resource "azurerm_api_management_api_operation_policy" "api_put_policy" {
api_name = azurerm_api_management_api.my_api.name
resource_group_name = azurerm_resource_group.main.name
api_management_name = azurerm_api_management.my_api.name
operation_id = azurerm_api_management_api_operation.my_api.operation_id
xml_content = templatefile("${path.root}/xml_content.tpl",
password = var.password
hostname_url = data.azurerm_function_app.MyFunctionApp.default_hostname
)
}
Whenever this is called, it will look for the placeholder values and replace them. Two additional things to note:
With the current setup, the "${other-route-variable}" would be required to be provided in the templatefile function call, otherwise it would fail.
The path.root option is built-in in Terraform [2].
In theory, if you were to create a module from this to make it more portable, then you would just have to change the path to the file so it can be provided through a variable probably.
[1] https://www.terraform.io/language/functions/templatefile
[2] https://www.terraform.io/language/expressions/references#filesystem-and-workspace-info

Use azure Apim to Call an Api that uses OAuth2 token

With Apim i'm trying to call a backend Api that needs a OAuth2 validation. This question are more or less similair to this: Azure API Management: Oauth2 with backend API
But there are no good answer here...
I have been reading alot about policies and caching.
But can't seem to set it up correctly. I hope to be able to cal the apim, and then the apim calls the backend api to get a token and with that token call an Api to get some output data.
I also found one where i had to setup some policies in the backend-part..
Can anyone help me set up the policies ?
my policy is like:
<policies>
<inbound>
<base />
<set-variable name="originBearer" value="#(context.Request.Headers.GetValueOrDefault("Authorization", "empty_token").Split(' ')[0].ToString())" />
<send-request ignore-error="true" timeout="20" response-variable-name="bearerToken" mode="new">
<set-url>{{lookupAccessTokenUrl}}</set-url>
<set-method>GET</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
<set-body>#{
return "client_id={{HLR-app-client-id}}&scope={{HLR-scope}}&client_secret={{HLR-secret}}&assertion="+(string)context.Variables["originBearer"]+"&grant_type=urn:ietf:params:oauth:grant-type:client_credentials&requested_token_use=on_behalf_of";
}</set-body>
</send-request>
<set-variable name="requestResponseToken" value="#((String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"])" />
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["requestResponseToken"])</value>
</set-header>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I found the answer to my own Question :-)
I try to comment on each line, but if you take alle the code and put it together you get a policy to handle Oauth2 in a backend api.
In the inbound section, the cache-lookup-value
Assigns the value in cache to the context variable called “bearerToken”.
On first entry, the cache value will be null and the variable will not be
created.
<inbound>
<cache-lookup-value key="cacheAccessToken" variable-name="bearerToken" />
Create a variable that contains clientid and secret - needed to call the api
<set-variable name="user-password" value="{{HLR-Clientid}}:{{HLR-Secret}}"
/>
<choose>
Checks if the context variable collection contains a key called
“bearerToken” and if not found executes the code between the opening and closing
“” XML elements.
<when condition="#(!context.Variables.ContainsKey("bearerToken"))">
Initiates the request to the OAuth endpoint with a response
timeout of 20 seconds. This will put the response message into the variable
called “oauthResponse”
<send-request mode="new" response-variable-name="oauthResponse" timeout="20" ignore-error="false">
<set-url>{{lookupAccessTokenUrl}}</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
here you define your header Authorization and use the variable that contains clientid and password
<set-header name="Authorization" exists-action="override">
<value>#("Basic " + system.Convert.ToBase64String(Encoding.UTF8.GetBytes((string)context.Variables["user-password"])))</value>
</set-header>
<set-body>#("grant_type=client_credentials&scope={{HLR-Scope}}")</set-body>
</send-request>
Casts the response as a JSON object to allow the retrieval of the “access_token” value using an indexer and assigns it to the context variable “accessToken”.
<set-variable name="AccessToken" value="#((string)((IResponse)context.Variables["oauthResponse"]).Body.As<JObject>()["access_token"])" />
Store result in cache and where we add the contents of the variable “accessToken” into cache for a period of 3600 seconds.
<cache-store-value key="cacheAccessToken" value="#((string)context.Variables["AccessToken"])" duration="3600" />
Set the variable in a context-variable, then it can be used right now
<set-variable name="bearerToken" value="#((string)context.Variables["AccessToken"])" />
</when>
</choose>
<base />
</inbound>
<backend>
<!--Creates the request to the backend web service. Here we are placing the response from the web service into the variable called “transferWSResponse”.-->
<send-request mode="copy" response-variable-name="transferWSResponse" timeout="60" ignore-error="false">
<set-method>GET</set-method>
<!--Is the creating the “Authorization” header to be sent with the request.-->
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["bearerToken"])</value>
</set-header>
<!--Removes the APIM subscription from being forwarded to the backend web service.-->
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
</send-request>
</backend>
<outbound>
<!--Now we need to return the response message from the backend web service to the caller. This is done in the “<outbound>” policy section. Here we just simply return the value of the variable “transferWSResponse” back to the caller-->
<return-response response-variable-name="transferWSResponse" />
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

How to use a variable when returning response in policy definition?

I'm configuring inbound policies in an instance of Azure API Management.
First, I set a variable:
<set-variable name="var1" value="" />
Then I send a request
<send-request mode="new" response-variable-name="var1" timeout="20" ignore-error="false">
Which returns a JSON. When testing I get the following message in trace tab:
GET request to 'https://my-api.azure-api.net/api/data' has been sent, result stored in 'var1' variable.
I guess the send-request policy works and the result is stored in the variable.
Then I want to return a response (still in inbound, I get 500 when trying to do it in outbound):
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>
{
"success": true,
"var1": context.Variables["var1"]
}
</set-body>
</return-response>
My problem is it doesn't work... It just renders context.Variables["var1"].
And so does:
#context.Variables["var1"]
#{ context.Variables.GetValueOrDefault<string>("var1") }
#context.Variables.GetValueOrDefault("var1")
All of them are rendered as written, no value is being extracted.
Edit: I also tried adding a placeholder string and then using
<find-and-replace from="Placeholder" to="context.Variables.GetValueOrDefault("var1")" />
And try to place it in inbound and outbound alike. But this policy did not launch.
It's a JSON object that I want to append to the response (small detail: in reality I have this issue with multiple variables).
My question is: how can I add my declared variable to the response?
There are two ways you can go about this. You could to use policy expressions for that: https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions. The thing to remember is that they can only be used to construct whole value for policy, not part of it, so:
<set-body>#("{\"success\": true, \"var1\": " + ((IResponse)context.Variables["var1"]).Body.As<string>() + "}"</set-body>
Or with set-body policy you could use liquid template:
<set-variable name="var1body" value="#((IResponse)context.Variables["var1"]).Body.As<string>())" />
<set-body template="liquid">
{
"success": true,
"var1": {{context.Variables["var1body"]}}
}
</set-body>
I assume you have some sidecar request going on additionally to your main request flow.
This sample adds the response from send-request to the response body of the main request:
<policies>
<inbound>
<base />
<!-- main request -->
<set-backend-service base-url="https://reqres.in" />
<rewrite-uri template="/api/users/2" />
<!-- sidecar request -->
<send-request mode="new" response-variable-name="var1" timeout="20" ignore-error="true">
<set-url>https://reqres.in/api/unkown/2</set-url>
<set-method>GET</set-method>
</send-request>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-body template="none">#{
var body = context.Response.Body.As<JObject>(true);
body["var1"] = ((IResponse)context.Variables["var1"]).Body.As<JObject>();
return body.ToString();
}</set-body>
</outbound>
<on-error>
<base />
</on-error>
</policies>

Rename Query Parameter in Outbound Request in Azure API Management

Using policies in Azure API Management, I'm trying to rename a query parameter, but it does not work. If I change copy-unmatched-params="false" to copy-unmatched-params="true" then it works, but the behaviour becomes that all unmatched parameters will get passed to the backend API which would allow clients to inject their own query params into the backend request which we do not want.
Everything else is fine.
I want to transform a request that comes in that looks like this:
https://{site}/search?query=dylan
To:
https://{backend-site}documents?api-version=2018-1-11&amount=1000&searchFields=Album,Artist&search=dylan
The only part that doesn't work is transforming the query parameter to be named "search" instead of query without allowing all parameters to be passed on from the inbound querystring. How can I fix that?
<policies>
<inbound>
<rewrite-uri template="/" copy-unmatched-params="false" />
<set-header name="api-key" exists-action="override">
<value>THIS-IS-API-KEI</value>
</set-header>
<set-query-parameter name="api-version" exists-action="override">
<value>2018-1-11</value>
</set-query-parameter>
<set-query-parameter name="amount" exists-action="override">
<value>1000</value>
</set-query-parameter>
<set-query-parameter name="searchFields" exists-action="override">
<value>Album,Artist</value>
</set-query-parameter>
<set-query-parameter name="search" exists-action="override">
<value>#(context.Request.Url.Query.GetValueOrDefault("query"))</value>
</set-query-parameter>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The reason why you're getting empty value from your last expression is because by that time your URI already rewritten to "/" and only "api-version", "amount", and "searchFields" query parameters are set. There are a few ways to go about that:
Refer to original Url: #(context.Request.OriginalUrl.Query.GetValueOrDefault("query"))
Add query to operation URI template - search?query={query} - and refer it from rewrite-uri policy: <rewrite-uri template="/?query={query}" copy-unmatched-params="false" />. The downside is that "query" parameter becomes required, so any request without it will result in 404.
Try using variables. Assign the value to variable in the beginning and use the variable for assigning new query parameter
You may rename parameter in the query by using string of the URL and simple replace method. In that case, the parameter wouldn't be mandatory.
<inbound>
<base />
<rewrite-uri template="#{
return "/some-url-here-or-your-previously-constructed-url" + context.Request.OriginalUrl.QueryString
.Replace("old-name", "new-name");
}" copy-unmatched-params="false" />
</inbound>

Loop through multiple values in Set-Header policy

Wondering if there's a way to loop through all values for a particular header and modify each one. I have the need to do a string replace on each value of a header and I want to avoid assuming how many values for that header there will be. I do not see a way to create a loop in API Management policies.
 <outbound>
<base />
<choose>
<when condition="#(context.Response.Headers.ContainsKey("Set-Cookie"))">
<set-header name="Set-Cookie" exists-action="override">
<value - How can I loop through these?>#{
var currentHeader = context.Response.Headers["Set-Cookie"][i];
//perform necessary string.replace
}</value>
</set-header>
</when>
</choose>
</outbound>

Resources