Exposing SAP Gateway services with API Management

Exposing services like the SAP Gateway is an important task for API Management but not always so easy. Security is often high around these products so we need to understand this in order to get the setup correct. To get started let’s look at the setup that we were facing. SAP Gateway is hosted inside the OnPrem network behind several firewalls and hard restrictions, API Management is hosted in Azure. In this case we had an Express Route setup ready to be used so we could deploy an API Management inside a preexisting subnet. Firewall rules where setup based on the local IP of the API Management instance (according to the information I have these should be static when deployed in a VNET).

After API Management is deployed inside the network, this done by setting the External Network in the network tab to a subnet of the VNET that is connected to the Expres Route. Make sure that API Management is alone in this subnet, see image for more informaton

After this is done we set up the routing/firewall rules, to get the local ip of the API Management instance we put up a vm with IIS and created a API inside API Managment that called the standard IIS start page, after that we searched the loggs in IIS to get the local IP.

Now we can start creating the API and I’ll jump right in to the policy settings of the API that is created to expose the SAP Gateway, the Security model on SAP GW can vary but we in this case we had Basic Authentication as the authentication mode. Adding handling of this is quite straightforward for API Management, there is a policy ready to be used “Authenticate with Basic”:

So we started off adding the authentication part, now the easy part is done. When calling the API we just got a 403 Forbidden response saying “Invalid X-CSRF-Token”, looking around this we found that it’s the Anti Forgery setup with SAP Gateway. To be able to handle this a token and a cookie is needed and the token and cookie are retrieved via a Successful GET call to the Gateway. The initial call is using the same URL (make sure that the GET operation is implemented so the result is successful (return 200 OK) otherwise the token is not valid. Since I had no access to the SAP GW without API Management my testing’s are done from Postman via API Management to SAP GW. Adding the “X-CSRF-Token” header with value Fetch will retrieve the token and cookie making the call like:

The response looks like:

The interesting part is the Headers and Cookies, let’s have a look, under Headers we find the X-CSRF-TOKEN that we need to use in the response back.

 

In the Cookies we find 2 items and we are interested in the one called “sap_xsrf..” this is the anti-cross site forgery cookie that is needed in the POST/PUT request to SAP Gateway.

Composing these makes a valid request look like this:

So now we can do these two requests to get a valid POST call in to SAP Gateway from Postman, so let’s move on to setting this up in API Management. In API Mangement we don’t want the client to understand this and/or force them to implement the two calls and the functionality to copy headers and so on, we just want to expose a “normal” API. So we need to configure API Management to do these two calls to the SAP Gateway from API Management in the same call from the client to be able send a valid POST request to SAP Gateway.

In order to make this work we need to use polices, so after setting up the standard POST request to create Service Order we will go to the Policy tab.

In the beginning it looks something like this:

So the first thing we need to do is to a send-request policy, I configured it wit mode new and used a variable name of fetchtokenresponse. Since retrieving the token is to do a GET request to the SAP Gatway we reuse the same URL to the API (after rewrite). Setting the header X-CSRF-Token to Fetch since we are fetching the token and adding the authorization header with the value for Basic authentication So let’s start with creating the call to do the GET token request. let’s add this code in the inbound section.

<send-request mode="new" response-variable-name="fetchtokenresponse" timeout="10″ ignore-error="false">*
<set-url>@(context.Request.Url.ToString())</set-url>
<set-method>GET</set-method>
<set-header name="X-CSRF-Token" exists-action="override">
<value>Fetch</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>Basic aaaaaaaaaaaaaaaaaaaaaaaaaa==</value>
</set-header>
<set-body></set-body>
</send-request>

Next step is to extract the values from the send-request operation and add them to our POST request, setting the X-CSRF-Token header is fairly straightforward so we use the set header policy and retrieves the header from the response variable, the code looks like:

<set-header name="X-CSRF-Token" exists-action="skip">
<value>@(((IResponse)context.Variables["fetchtokenresponse"]).Headers.GetValueOrDefault("x-csrf-token"))</value>
</set-header>

A bit trickier is the Cookie, since there are no “standard” cookie handler we need to implement some more logic, in the sample I provide I just made a lot of assumptions on the cookie. We need the cookie starting with sap_XSRF I started by splitting all cookies on ‘;’ and finding the cookie that contained “sap_XSRF”, this had in our case also a domain that I didn’t need so I removed it by splitting on ‘,’ (comma) and used the result in a set-header policy.

<set-header name="Cookie" exists-action="skip">
<value>@{
string rawcookie = ((IResponse)context.Variables["fetchtokenresponse"]).Headers.GetValueOrDefault("Set-Cookie");
string[] cookies = rawcookie.Split(';');
string xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("sap-XSRF"));
return xsrftoken.Split(',')[1];
}
</value>
</set-header>

All in all the policy will look like:

 <policies>
 <inbound>
 <base />
 <rewrite-uri template="sap/opu/odata/sap/ZCAV_AZURE_CS_ORDER_SRV/ItHeaderSet('{oid}')" />
 <send-request mode="new" response-variable-name="fetchtokenresponse" timeout="10" ignore-error="false">
 <set-url>@(context.Request.Url.ToString())</set-url>
 <set-method>GET</set-method>
 <set-header name="X-CSRF-Token" exists-action="override">
 <value>Fetch</value>
 </set-header>
 <set-header name="Authorization" exists-action="override">
 <value>Basic aaaaaaaaaaaaaaaaaaaaaaaaaa ==</value>
 </set-header>
 <set-body>
 </set-body>
 </send-request>
 <set-header name="X-CSRF-Token" exists-action="skip">
 <value>@(((IResponse)context.Variables["fetchtokenresponse"]).Headers.GetValueOrDefault("x-csrf-token"))</value>
	 </set-header>
 <set-header name="Cookie" exists-action="skip">
 <value>@{
 string rawcookie = ((IResponse)context.Variables["fetchtokenresponse"]).Headers.GetValueOrDefault("Set-Cookie");
 string[] cookies = rawcookie.Split(';');
 string xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("sap-XSRF"));
 return xsrftoken.Split(',')[1];
 }
 </value>
</set-header>
 </inbound>
 <backend>
<base />
</backend>
<outbound>
 <base />
 </outbound>
 <on-error>
 <base />
 </on-error>
 </policies>

The result is now complete and the client will assume/think that this is just a regular API as the other ones and we can expose this with regular API Management API-Key security.

Wrap up

Exposing the SAP Gateway is not a straight forward task but after understanding the process and being able to implement the SAP Gateway complexity inside API Management we can expose these functions just as any other API, I would suggest adding some Rate call limits and quotas to protect the gateway from overflow. This scenario proves the value of API Management and provides possiblities to solve complex authentication and anti forgery patterns in order to standardise your own API Facade with the same auth mechanism for the client without tacking the backend security in consideration.

Full scenario:

 

Posted in: •Azure API Management  •Integration  •Uncategorized  | Tagged: •API Governance  •API Management  •SAP