What is the approach to a simple append to the url of a context variable such as context.Variables["accountKey"] during a policy rewrite?
The end result should be /accounts/232.
I have success earlier in setting it
set-variable (0.003 ms)
{
"message": "Context variable was successfully set.",
"name": "accountKey",
"value": "232"
}
One of the things tried:
<policies>
<inbound>
<base />
<rewrite-uri template="/accounts/{accountKey}" />
</inbound>
But I get this error
> Error Receive
> rewrite-uri (0.260 ms) {
> "messages": [
> null,
> "Variable accountKey has no value.",
> "Variable accountKey has no value."
> ] }
Configure the inbound rule in the policy as follows:
<inbound>
<base />
<set-variable name="accountKey" value="232" />
<rewrite-uri template="#{
return "/account/" + context.Variables.GetValueOrDefault<string>("accountKey");
}"/>
</inbound>
{} in rewrite-uri are for query string parameters in the original request URL.
Find more details about rewrite-uri section in the Microsoft docs at Rewrite URL - API Management transformation policies.
I ended up using this call as shown as an alternative:
<rewrite-uri template="#($"/account/{(string)context.Variables["accountKey"]}/")" />
In my case, my URL was
https://test.com/send/
I need to add query string from context variable name (name = myName)
https://test.com/send/myName
This is working for me:
<rewrite-uri template="#($"{(string)context.Variables["name"]}")" />
Related
I have SOAP service that I have converted to rest in apim and returns the response in json using the following outbound policy:
<outbound>
<base />
<xml-to-json kind="direct" apply="always" consider-accept-header="false" />
</outbound>
Response output is like below:
"response": {"currentABNRecord": {
"ABN":
{
"identifierValue": "xxxxx",
"identifierStatusCode": "ACT",
"issuingPartyCode": null,
"replacedIndicator": "N"
}
}
}
My requirement is to "manipulate" conditionally data by modifying "identifierStatusCode" and appending a new attribute "termsAnConditions" when "identifierStatusCode" is "ACT" and if it is DEL to "DELETED" to something else like below :
"response": {"currentABNRecord": {
"ABN":
{
"identifierValue": "xxxxx",
"identifierStatusCode": "ACT",
"issuingPartyCode": null,
"replacedIndicator": "N",
"termsAnConditions": "Some terms and conditions"
}
}
}
How can I call an azure function(perform all the conditional logic in it ) in the outbound policy and return the modified object. Is this the correct approach or can I do it policies.
In APIM, I am trying to access the Product information at API level policy, as i have to execute some logic based on product name. I am using the below code
<policies>
<inbound>
<set-variable name="ProductName" value="#{
return context.Product.Name;
}" />
But, when trying to post request from postman, i can see the below exception in trace.
{
source: "set-variable",
timestamp: "2020-08-19T14:42:24.4936554Z",
elapsed: "00:00:00.0358409",
data:- {
messages:- [
-{
message: "Expression evaluation failed.",
expression: " return context.Product.Name; ",
details: "Object reference not set to an instance of an object."
},
"Expression evaluation failed. Object reference not set to an instance of an object.",
"Object reference not set to an instance of an object."
]
}
}
Why it is null ? is this the case, that i can't access the property in inbound scope. Need guidance. Or, is there any other way i can access the Product.Name property.
Thank you.
You can get the Product Name with #(context.Product.Name).
<inbound>
<base />
<set-variable name="aaa" value="#(context.Product.Name)" />
<set-body template="liquid">
{
"success": true,
"var1": {{context.Variables["aaa"]}}
}
</set-body>
</inbound>
In test, set the product name as Starter and you will get the snapshot as below.
I've picked up something that someone else has set up.
We have an API Management instance, siting behind an Application Gateway, which has a policy on an API:
<inbound>
<choose>
<when condition="#(context.Request.Certificate == null)">
<return-response>
<set-status code="403" reason="Client certificate required..d1PD" />
</return-response>
</when>
</choose>
<choose>
<when condition="#(!context.Request.Certificate.Verify())">
<return-response>
<set-status code="403" reason="Client certificate cannot be verified..d2PD " />
</return-response>
</when>
</choose>
<choose>
<when condition="#(!context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint))">
<return-response>
<set-status code="403" reason="Client certificate is untrusted or invalid..d3PD" />
</return-response>
</when>
</choose>
<base />
</inbound>
In Postman I am passing a cert and key. The Postman Console shows
Client Certificate:
keyPath:"C:\selfsigned\internalscm.X.com.key"
pemPath:"C:\selfsigned\internalscm.X.com.crt"
pfxPath:""
I'm passing Ocp-Apim-Trace in the header of the request so am getting a trace back which contains:
traceEntries {2}
inbound [10]
..
6 {4}
source : authentication-certificate
timestamp : 2019-08-06T08:55:31.3435485Z
elapsed : 00:00:00.0006857
data {2}
message : Certificate was attached to request per configuration.
certificate {...}
7 {4}
source : choose
timestamp : 2019-08-06T08:55:31.3435485Z
elapsed : 00:00:00.0007011
data {3}
message : Expression was successfully evaluated.
expression : context.Request.Certificate == null
value : true
UPDATE:
The authentication-certificate assessment is of the backend's certificate and is nothing to do with the client certificate (.key and .crt) that Postman claims to be including in the request (the same result is returned if I pass a pfx and password instead of .key and .crt).
When I hit the API that the Gateway is protecting, I can see in the trace that it is processing the client certificate (and returning a 200):
{
"source": "client-certificate-handler",
"timestamp": "2019-08-09T15:47:46.3825928Z",
"elapsed": "00:00:00.0005974",
"data": "Requesting client certificate because next handler requires access to it."
},
{
"source": "client-certificate-handler",
"timestamp": "2019-08-09T15:47:46.6950495Z",
"elapsed": "00:00:00.3225172",
"data": "Client certificate thumbprint '6C03F4E7999999999999999999999999' received."
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.6950495Z",
"elapsed": "00:00:00.3225288",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "context.Request.Certificate == null",
"value": false
}
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.9606395Z",
"elapsed": "00:00:00.5849700",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "!context.Request.Certificate.Verify()",
"value": false
}
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.9606395Z",
"elapsed": "00:00:00.5850060",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "!context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint)",
"value": false
}
}
So it looks like AppGateway is dropping the client certificate.
The trace doesn't give me enough to start deducing why the client certificate (assuming that Postman is transmitting it to the Gateway as it does the API) is being dropped. Where should I start?
For ref, when I remove the policy the request is processed as expected.
I'm not sure if you can make AppGateway pass the certificate - you need to check their docs. The reason I'm skeptical is bacuse the whole idea of AppGateway is to look into traffic and provide protection by doing that. And the only way to d othat is to terminate SSL connection at AppGateway level. See here for more information: https://learn.microsoft.com/en-us/azure/application-gateway/ssl-overview there are two modes for AppGateway: SSL temination when AppGateway makes HTTP (not HTTPS) calls to backend, and SSL end-to-end when AppGateway uses own SSL certificate to connect to backend.
Some client certificate information can be passed to backend via server variables: https://learn.microsoft.com/en-us/azure/application-gateway/rewrite-http-headers-url#mutual-authentication-server-variables
I am trying to use ARM templates to deploy my API management service and have everything working except policyContent. Basically it wants the policyContent as "Json escaped Xml Encoded contents of the Policy." This is very hard to maintain and was trying to find a way to take an XML file and inject the contents into this string, or some better way. I would like not to have to write a program to maintain these strings because it feels like something that shouldn't be so complicated.
Policy Reference
Example with string
{
"name": "policy",
"type": "Microsoft.ApiManagement/service/apis/policies",
"apiVersion": "2017-03-01",
"properties": {
"policyContent": "string"
}
}
You can maintain your policies into an XML file and reference it like this:
{
"apiVersion": "2018-01-01",
"name": "policy",
"type": "Microsoft.ApiManagement/service/policies",
"properties": {
"policyContent": "[concat(parameters('repoBaseUrl'), '/policy.xml')]",
"contentFormat": "rawxml-link"
},
"dependsOn": [
"[resourceId('Microsoft.ApiManagement/service/', parameters('ApimServiceName'))]"
]
}
Your policy.xml file must be available online and will look like this:
<policies>
<inbound>
<rate-limit calls="3" renewal-period="10" />
<base />
</inbound>
<outbound>
<base />
</outbound>
<backend>
<base />
</backend>
<on-error>
<base />
</on-error>
</policies>
Well, the only thing I can think of (because nothing native in arm templates can help you) is read the input from a file and convert it to JSON:
$xml = (Get-Content file -Raw).ToString()
($xml | ConvertTo-Json -Compress) -replace '\\u003c','<' ) -replace '\\u003e','>'
It might work without replacing those unicodes back to <>, no idea.
I want to log to log the request and the response so I place a one-way-request in the inbound and the outbound section:
<policies>
<inbound>
<send-one-way-request mode="new">
<set-url>#("example.com")</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#(context.Request.Body.As<string>(preserveContent: true))</set-body>
</send-one-way-request>
</inbound>
<backend>
<base />
</backend>
<outbound>
<send-one-way-request mode="new">
<set-url>#("example.com")</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#(context.Response.Body.As<string>(preserveContent: true))</set-body>
</send-one-way-request>
</outbound>
</policies>
identical call, except the body.
When I check the trace I see this in the inbound section:
send-one-way-request (0 ms)
"One way request was successfully send to https://...."
forward-request (9690 ms)
{
"response": {
"status": {
"code": 200,
"reason": "OK"
},
"headers": [
{
"name": "Pragma",
"value": "no-cache"
},
{
"name": "Content-Length",
"value": "2"
},
{
"name": "Cache-Control",
"value": "no-cache"
},
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"name": "Date",
"value": "Wed, 05 Jul 2017 07:56:14 GMT"
},
{
"name": "Expires",
"value": "-1"
},
{
"name": "Server",
"value": "Microsoft-IIS/8.0"
},
{
"name": "X-AspNet-Version",
"value": "4.0.30319"
},
{
"name": "X-Powered-By",
"value": "ASP.NET"
}
]
}
}
but in the outbound I only get:
send-one-way-request (0 ms)
"One way request was successfully send to https://...."
and no forward request. Because I use a one way request I don't expect a response from the calls and I can't remember to have the forward-request in the inbound part (didn't found them in a saved traced call with a one way request in the inbound).
Is there maybe a configuration anything else that triggers a forward-request?
Edit:
I use the azure function to handle this. When I make a typo in the subdomain the forward-request disappears, but when I make a typo in the function name it is still there... Both requests are directed to the same azure function.
Edit2:
This is getting more confuse: when I send the default body from the swagger file the request-forward is not there. If I repeat the request or if i modify the default body it appears...
Since send-one-way request policy is completely asynchronous in regards to request processing itself, there is no guarantee that reply from such request will be logged, because by the time it is received the request itself may be long processed, response returned to client along with tracing information. So it works as a best effort, if by the time when response to send-one-way-request is received the main request is still being processed response will be logged, otherwise not.
In the first example reply to one-way request in inbound is logged because there is still a lot processing to be done on the main request - send request to backend, process outbound section. But when one-way request is placed as the last statement in outbound, right before response to the client is sent - response to one-way request will come too late to be placed into trace.
Typo in subdomain may trigger longer connection time, if such domain not actively refuses connections, thus will stall response, thus it will disappear from trace.
It's all a question of timing. If you want to make sure that main request processing is stalled until you get response from these side requests use send-request policy instead.