Skip to content

Commit 1982d81

Browse files
committed
Proxy causes an infinite loop when the redirected URI is a targeted for a local function or proxy
which doesn't exist #2402
1 parent 5c7c54a commit 1982d81

File tree

4 files changed

+42
-1
lines changed

4 files changed

+42
-1
lines changed

src/WebJobs.Script.WebHost/ProxyFunctionExecutor.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Linq;
78
using System.Net;
89
using System.Net.Http;
910
using System.Threading;
@@ -55,6 +56,27 @@ public async Task ExecuteFuncAsync(string funcName, Dictionary<string, object> a
5556
return await functionRequestInvoker.ProcessRequestAsync(req, ct, _scriptHostManager, _webHookReceiverManager);
5657
};
5758

59+
// Local Function calls do not go thru ARR, so implementing the ARR's MAX-FORWARDs header logic here to avoid infinte redirects.
60+
IEnumerable<string> values = null;
61+
int redirectCount = 0;
62+
if (request.Headers.TryGetValues(ScriptConstants.AzureProxyFunctionLocalRedirectHeaderName, out values))
63+
{
64+
int.TryParse(values.FirstOrDefault(), out redirectCount);
65+
66+
if(redirectCount >= ScriptConstants.AzureProxyFunctionMaxLocalRedirects)
67+
{
68+
response = request.CreateErrorResponse(HttpStatusCode.BadRequest, "Infinite loop detected when trying to call a local function or proxy from a proxy.");
69+
request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey] = response;
70+
return;
71+
}
72+
73+
// This is to make sure the header is properly updated. removing it then adding it with updated count.
74+
request.Headers.Remove(ScriptConstants.AzureProxyFunctionLocalRedirectHeaderName);
75+
}
76+
77+
redirectCount++;
78+
request.Headers.Add(ScriptConstants.AzureProxyFunctionLocalRedirectHeaderName, redirectCount.ToString());
79+
5880
var resp = await _scriptHostManager.HttpRequestManager.ProcessRequestAsync(request, processRequestHandler, cancellationToken);
5981
request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey] = resp;
6082
return;

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public static class ScriptConstants
6363
public const string DynamicSku = "Dynamic";
6464
public const string DefaultProductionSlotName = "production";
6565

66+
public const string AzureProxyFunctionLocalRedirectHeaderName = "X_PROXY_LOCAL_REDIRECT_COUNT";
67+
public const int AzureProxyFunctionMaxLocalRedirects = 10;
68+
6669
public const string FeatureFlagDisableShadowCopy = "DisableShadowCopy";
6770
public const string FeatureFlagsEnableDynamicExtensionLoading = "EnableDynamicExtensionLoading";
6871

test/WebJobs.Script.Tests.Integration/ProxyEndToEndTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ public async Task LocalFunctionCall()
7373
Assert.Equal("Pong", content);
7474
}
7575

76+
[Fact]
77+
public async Task LocalFunctionInfiniteRedirectTest()
78+
{
79+
HttpResponseMessage response = await _fixture.HttpClient.GetAsync($"api/myloop");
80+
81+
string content = await response.Content.ReadAsStringAsync();
82+
Assert.Equal("400", response.StatusCode.ToString("D"));
83+
Assert.True(content.Contains("Infinite loop"));
84+
}
85+
7686
[Fact]
7787
public async Task LocalFunctionCallWithoutProxy()
7888
{

test/WebJobs.Script.Tests.Integration/TestScripts/Proxies/proxies.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"catchAllRoutes": {
7070
"matchCondition": {
7171
"route": "/proxy/{*path}",
72-
"methods": ["GET", "POST"]
72+
"methods": [ "GET", "POST" ]
7373
},
7474
"backendUri": "http://localhost/{path}",
7575
"requestOverrides": {
@@ -105,6 +105,12 @@
105105
"requestOverrides": {
106106
"backend.request.headers.accept": "text/plain"
107107
}
108+
},
109+
"ProxyAvoidInfiniteRedirect": {
110+
"matchCondition": {
111+
"route": "/api/{*path}"
112+
},
113+
"backendUri": "https://localhost/api/{path}"
108114
}
109115
}
110116
}

0 commit comments

Comments
 (0)