The APIM ip-filter
policy doesn't support changing to check X-Forwarded-For
instead of request IP. I've tried using the check-header
policy, but I get the X-Forwarded-For
header with port suffixed <ip>:<port>
. I haven't found a way to make the check-header
check more advanced to take care of this (removing the port part).
What I've come up with is this, but I don't like it, very bulky and a big if clause.
Can anyone see how this can be solved in a simpler and neater way?
<inbound>
<base />
<set-variable name="allowedIPs" value="192.192.192.192" />
<!-- Determine the IP address to check -->
<set-variable name="clientIP" value="@(context.Request.Headers.GetValueOrDefault("X-Forwarded-For", context.Request.IpAddress))" />
<!-- The regular expression ^([\d\.]+) captures only the numeric part of the IP address (i.e., the part before any colon : that would indicate the port). -->
<set-variable name="cleanClientIP" value="@(System.Text.RegularExpressions.Regex.Match((string)context.Variables["clientIP"], @"^([\d\.]+)").Value)" />
<!-- Function to check if the IP is allowed -->
<choose>
<when condition="@(context.Variables.GetValueOrDefault<string>("allowedIPs").Split(',').Contains(context.Variables.GetValueOrDefault<string>("cleanClientIP")))">
<!-- REQUEST IS OK, ADD FURTHER LOGIC HERE -->
</when>
<otherwise>
<return-response>
<set-status code="403" reason="Forbidden" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>{
"statusCode": 403,
"message": "Not authorized"
}</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
What I mainly want to achieve is:
This you get by using check-header
or ip-filter
. See example below:
<check-header name="header name" failed-check-httpcode="code" failed-check-error-message="message" ignore-case="true | false">
<value>Value1</value>
<value>Value2</value>
</check-header>
Simplify the policy by avoiding regular expressions altogether. By splitting the header value using Split(':')
, the policy becomes much cleaner and easier to read.
Simplified APIM policy:`
<inbound>
<base />
<!-- Define the list of allowed IPs -->
<set-variable name="allowedIPs" value="192.192.192.192,203.0.113.0" />
<!-- Extract the client IP, removing the port if present -->
<set-variable name="cleanClientIP" value="@(context.Request.Headers.GetValueOrDefault("X-Forwarded-For", context.Request.IpAddress).Split(':')[0])" />
<!-- Check if the cleaned client IP is in the list of allowed IPs -->
<choose>
<when condition="@(context.Variables["allowedIPs"].Split(',').Contains(context.Variables["cleanClientIP"]))">
<!-- REQUEST IS OK, ADD FURTHER LOGIC HERE -->
</when>
<otherwise>
<!-- Deny the request if the IP is not allowed -->
<return-response>
<set-status code="403" reason="Forbidden" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>
{
"statusCode": 403,
"message": "Not authorized"
}
</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
X-Forwarded-For: 192.168.1.1
Policy extracts 192.168.1.1
from the header and matches it with the allowedIPs
list.
Response:
{
"message": "Request successfully processed"
}
Valid IP with Port:
192.168.1.1
by splitting the header value at the colon (:
) It matches 192.168.1.1
with the allowedIPs
list.Response:
{
"message": "Request successfully processed"
}
Aswell as it works with Multiple IPs in X-Forwarded-For Header also.
Modified:
<inbound>
<base />
<!-- Rewrite X-Forwarded-For to remove any port information -->
<set-header
name="X-Forwarded-For"
exists-action="override"
value="@(context.Request.Headers.GetValueOrDefault('X-Forwarded-For', context.Request.IpAddress).Split(':')[0])" />
<!-- Early exit if the IP is not in the allowed list -->
<check-header
name="X-Forwarded-For"
failed-check-httpcode="403"
failed-check-error-message="Not authorized"
ignore-case="false">
<value>192.192.192.192</value>
<value>192.192.192.193</value>
<!-- Additional allowed IPs can be added here -->
</check-header>
<!-- Further processing can go here -->
</inbound>
<set-header>
rewrites the X-Forwarded-For
by splitting its value on the colon (:) and taking the first part, which is the actual IP address without the port.Now, the policy checks if the cleaned IP value matches any of the allowed IPs listed as separate <value>
entries. If there's no match, it automatically returns a 403 response with your custom error message.