Skip to content

Commit 810d4af

Browse files
authored
fix: Better Lambda web request input parameter validation. (#2653)
Fixes #2652
1 parent 369dcba commit 810d4af

File tree

8 files changed

+109
-3
lines changed

8 files changed

+109
-3
lines changed

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AwsLambda/HandlerMethodWrapper.cs

+49-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ private class FunctionDetails
2525
public AwsLambdaEventType EventType { get; private set; } = AwsLambdaEventType.Unknown;
2626

2727
public bool HasContext() => ContextIdx != -1;
28-
public bool HasInputObject() => InputIdx != -1;
28+
private bool HasInputObject() => InputIdx != -1;
2929

3030
public void SetContext(object lambdaContext, int contextIdx)
3131
{
@@ -112,6 +112,41 @@ public object GetInputObject(InstrumentedMethodCall instrumentedMethodCall)
112112
}
113113

114114
public bool IsWebRequest => EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest;
115+
116+
public bool ValidateWebRequestParameters(InstrumentedMethodCall instrumentedMethodCall)
117+
{
118+
if (HasInputObject() && IsWebRequest)
119+
{
120+
dynamic input = GetInputObject(instrumentedMethodCall);
121+
122+
// make sure the request includes Http Method and Path
123+
switch (EventType)
124+
{
125+
case AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest:
126+
{
127+
if (input.RequestContext != null)
128+
{
129+
dynamic requestContext = input.RequestContext;
130+
131+
return !string.IsNullOrEmpty(requestContext.Http.Method) && !string.IsNullOrEmpty(requestContext.Http.Path);
132+
}
133+
134+
return false;
135+
}
136+
case AwsLambdaEventType.APIGatewayProxyRequest:
137+
case AwsLambdaEventType.ApplicationLoadBalancerRequest:
138+
{
139+
dynamic webReq = input;
140+
return !string.IsNullOrEmpty(webReq.HttpMethod) && !string.IsNullOrEmpty(webReq.Path);
141+
}
142+
default:
143+
return true;
144+
}
145+
146+
}
147+
148+
return false;
149+
}
115150
}
116151

117152
private List<string> _webResponseHeaders = ["content-type", "content-length"];
@@ -194,8 +229,19 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
194229
}
195230
}
196231

232+
if (_functionDetails!.IsWebRequest)
233+
{
234+
if (!_functionDetails.ValidateWebRequestParameters(instrumentedMethodCall))
235+
{
236+
agent.Logger.Debug($"Invalid or missing web request parameters. HttpMethod and Path are required for {_functionDetails.EventType}. Not instrumenting this function invocation.");
237+
return Delegates.NoOp;
238+
}
239+
}
240+
241+
242+
197243
var isAsync = instrumentedMethodCall.IsAsync;
198-
string requestId = _functionDetails!.GetRequestId(instrumentedMethodCall);
244+
string requestId = _functionDetails.GetRequestId(instrumentedMethodCall);
199245
var inputObject = _functionDetails.GetInputObject(instrumentedMethodCall);
200246

201247
transaction = agent.CreateTransaction(
@@ -314,7 +360,7 @@ private void CaptureResponseData(ITransaction transaction, object response, IAge
314360
{
315361
// copy and lowercase the headers
316362
Dictionary<string, string> copiedHeaders = new Dictionary<string, string>();
317-
foreach(var kvp in responseHeaders)
363+
foreach (var kvp in responseHeaders)
318364
copiedHeaders.Add(kvp.Key.ToLower(), kvp.Value);
319365

320366
foreach (var header in _webResponseHeaders) // only capture specific headers

tests/Agent/IntegrationTests/IntegrationTestHelpers/AgentLogBase.cs

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public abstract class AgentLogBase
7676
// Serverless payloads
7777
public const string ServerlessPayloadLogLineRegex = FinestLogLinePrefixRegex + @"Serverless payload: (.*)";
7878

79+
// Invalid serverless web request
80+
public const string InvalidServerlessWebRequestLogLineRegex = DebugLogLinePrefixRegex + @"Invalid or missing web request parameters. (.*)";
81+
7982
public AgentLogBase(ITestOutputHelper testLogger)
8083
{
8184
_testLogger = testLogger;

tests/Agent/IntegrationTests/IntegrationTests/AwsLambda/AwsLambdaAPIGatewayHttpApiV2ProxyRequestTest.cs

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ protected AwsLambdaAPIGatewayHttpApiV2ProxyRequestTest(T fixture, ITestOutputHel
3535
_fixture.EnqueueAPIGatewayHttpApiV2ProxyRequest();
3636
_fixture.EnqueueAPIGatewayHttpApiV2ProxyRequestWithDTHeaders(TestTraceId, TestParentSpanId);
3737
_fixture.EnqueueMinimalAPIGatewayHttpApiV2ProxyRequest();
38+
_fixture.EnqueueInvalidAPIGatewayHttpApiV2ProxyRequest();
3839
_fixture.AgentLog.WaitForLogLines(AgentLogBase.ServerlessPayloadLogLineRegex, TimeSpan.FromMinutes(1), 3);
3940
}
4041
);
@@ -47,13 +48,18 @@ public void Test()
4748
var serverlessPayloads = _fixture.AgentLog.GetServerlessPayloads().ToList();
4849

4950
Assert.Multiple(
51+
// the fourth exerciser invocation should result in a NoOpDelegate, so there will only be 3 payloads
5052
() => Assert.Equal(3, serverlessPayloads.Count),
5153
// validate the first 2 payloads separately from the 3rd
5254
() => Assert.All(serverlessPayloads.GetRange(0, 2), ValidateServerlessPayload),
5355
() => ValidateMinimalRequestPayload(serverlessPayloads[2]),
5456
() => ValidateTraceHasNoParent(serverlessPayloads[0]),
5557
() => ValidateTraceHasParent(serverlessPayloads[1])
5658
);
59+
60+
// verify that the invalid request payload generated the expected log line
61+
var logLines = _fixture.AgentLog.TryGetLogLines(AgentLogBase.InvalidServerlessWebRequestLogLineRegex);
62+
Assert.Single(logLines);
5763
}
5864

5965
private void ValidateServerlessPayload(ServerlessPayload serverlessPayload)

tests/Agent/IntegrationTests/IntegrationTests/AwsLambda/AwsLambdaAPIGatewayRequestTest.cs

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ protected AwsLambdaAPIGatewayProxyRequestTest(T fixture, ITestOutputHelper outpu
3535
_fixture.EnqueueAPIGatewayProxyRequest();
3636
_fixture.EnqueueAPIGatewayProxyRequestWithDTHeaders(TestTraceId, TestParentSpanId);
3737
_fixture.EnqueueMinimalAPIGatewayProxyRequest();
38+
_fixture.EnqueueInvalidAPIGatewayProxyRequest();
3839
_fixture.AgentLog.WaitForLogLines(AgentLogBase.ServerlessPayloadLogLineRegex, TimeSpan.FromMinutes(1), 3);
3940
}
4041
);
@@ -47,13 +48,18 @@ public void Test()
4748
var serverlessPayloads = _fixture.AgentLog.GetServerlessPayloads().ToList();
4849

4950
Assert.Multiple(
51+
// the fourth exerciser invocation should result in a NoOpDelegate, so there will only be 3 payloads
5052
() => Assert.Equal(3, serverlessPayloads.Count),
5153
// validate the first 2 payloads separately from the 3rd
5254
() => Assert.All(serverlessPayloads.GetRange(0, 2), ValidateServerlessPayload),
5355
() => ValidateMinimalRequestPayload(serverlessPayloads[2]),
5456
() => ValidateTraceHasNoParent(serverlessPayloads[0]),
5557
() => ValidateTraceHasParent(serverlessPayloads[1])
5658
);
59+
60+
// verify that the invalid request payload generated the expected log line
61+
var logLines = _fixture.AgentLog.TryGetLogLines(AgentLogBase.InvalidServerlessWebRequestLogLineRegex);
62+
Assert.Single(logLines);
5763
}
5864

5965
private void ValidateServerlessPayload(ServerlessPayload serverlessPayload)

tests/Agent/IntegrationTests/IntegrationTests/AwsLambda/AwsLambdaApplicationLoadBalancerRequestTest.cs

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ protected AwsLambdaApplicationLoadBalancerRequestTest(T fixture, ITestOutputHelp
3434
{
3535
_fixture.EnqueueApplicationLoadBalancerRequest();
3636
_fixture.EnqueueApplicationLoadBalancerRequestWithDTHeaders(TestTraceId, TestParentSpanId);
37+
_fixture.EnqueueInvalidLoadBalancerRequestyRequest();
3738
_fixture.AgentLog.WaitForLogLines(AgentLogBase.ServerlessPayloadLogLineRegex, TimeSpan.FromMinutes(1), 2);
3839
}
3940
);
@@ -46,11 +47,16 @@ public void Test()
4647
var serverlessPayloads = _fixture.AgentLog.GetServerlessPayloads().ToList();
4748

4849
Assert.Multiple(
50+
// the third exerciser invocation should result in a NoOpDelegate, so there will only be 2 payloads
4951
() => Assert.Equal(2, serverlessPayloads.Count),
5052
() => Assert.All(serverlessPayloads, ValidateServerlessPayload),
5153
() => ValidateTraceHasNoParent(serverlessPayloads[0]),
5254
() => ValidateTraceHasParent(serverlessPayloads[1])
5355
);
56+
57+
// verify that the invalid request payload generated the expected log line
58+
var logLines = _fixture.AgentLog.TryGetLogLines(AgentLogBase.InvalidServerlessWebRequestLogLineRegex);
59+
Assert.Single(logLines);
5460
}
5561

5662
private void ValidateServerlessPayload(ServerlessPayload serverlessPayload)

tests/Agent/IntegrationTests/IntegrationTests/RemoteServiceFixtures/AwsLambda/LambdaAPIGatewayHttpApiV2ProxyRequestTriggerFixture.cs

+13
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,19 @@ public void EnqueueMinimalAPIGatewayHttpApiV2ProxyRequest()
203203
""";
204204
EnqueueLambdaEvent(apiGatewayProxyRequestJson);
205205
}
206+
207+
/// <summary>
208+
/// An invalid payload to validate the fix for https://github.com/newrelic/newrelic-dotnet-agent/issues/2652
209+
/// </summary>
210+
public void EnqueueInvalidAPIGatewayHttpApiV2ProxyRequest()
211+
{
212+
var invalidApiGatewayHttpApiV2ProxyRequestJson = $$"""
213+
{
214+
"foo": "bar"
215+
}
216+
""";
217+
EnqueueLambdaEvent(invalidApiGatewayHttpApiV2ProxyRequestJson);
218+
}
206219
}
207220

208221
public class LambdaAPIGatewayHttpApiV2ProxyRequestTriggerFixtureNet6 : LambdaAPIGatewayHttpApiV2ProxyRequestTriggerFixtureBase

tests/Agent/IntegrationTests/IntegrationTests/RemoteServiceFixtures/AwsLambda/LambdaAPIGatewayProxyRequestTriggerFixture.cs

+13
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,19 @@ public void EnqueueMinimalAPIGatewayProxyRequest()
166166
""";
167167
EnqueueLambdaEvent(apiGatewayProxyRequestJson);
168168
}
169+
170+
/// <summary>
171+
/// An invalid payload to validate the fix for https://github.com/newrelic/newrelic-dotnet-agent/issues/2652
172+
/// </summary>
173+
public void EnqueueInvalidAPIGatewayProxyRequest()
174+
{
175+
var invalidApiGatewayProxyRequestJson = $$"""
176+
{
177+
"foo": "bar"
178+
}
179+
""";
180+
EnqueueLambdaEvent(invalidApiGatewayProxyRequestJson);
181+
}
169182
}
170183

171184
public class LambdaAPIGatewayProxyRequestTriggerFixtureNet6 : LambdaAPIGatewayProxyRequestTriggerFixtureBase

tests/Agent/IntegrationTests/IntegrationTests/RemoteServiceFixtures/AwsLambda/LambdaApplicationLoadBalancerRequestTriggerFixture.cs

+13
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ public void EnqueueApplicationLoadBalancerRequestWithDTHeaders(string traceId, s
9393
""";
9494
EnqueueLambdaEvent(ApplicationLoadBalancerRequestJson);
9595
}
96+
97+
/// <summary>
98+
/// An invalid payload to validate the fix for https://github.com/newrelic/newrelic-dotnet-agent/issues/2652
99+
/// </summary>
100+
public void EnqueueInvalidLoadBalancerRequestyRequest()
101+
{
102+
var invalidLoadBalancerRequestJson = $$"""
103+
{
104+
"foo": "bar"
105+
}
106+
""";
107+
EnqueueLambdaEvent(invalidLoadBalancerRequestJson);
108+
}
96109
}
97110

98111
public class LambdaApplicationLoadBalancerRequestTriggerFixtureNet6 : LambdaApplicationLoadBalancerRequestTriggerFixtureBase

0 commit comments

Comments
 (0)