Skip to content

Commit f71521f

Browse files
authored
fix: Format and log audit-level messages only when audit logging is enabled. (#1734)
1 parent 1a56612 commit f71521f

File tree

4 files changed

+139
-10
lines changed

4 files changed

+139
-10
lines changed

src/Agent/NewRelic/Agent/Core/DataTransport/HttpCollectorWire.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,14 @@ public string SendData(string method, ConnectionInfo connectionInfo, string seri
156156
}
157157
}
158158

159-
private static void AuditLog(Direction direction, Source source, string uri)
159+
private void AuditLog(Direction direction, Source source, string uri)
160160
{
161-
var message = string.Format(AuditLogFormat, direction, source, Strings.ObfuscateLicenseKeyInAuditLog(uri, LicenseKeyParameterName));
162-
Logging.AuditLog.Log(message);
161+
if (Logging.AuditLog.IsAuditLogEnabled)
162+
{
163+
var message = string.Format(AuditLogFormat, direction, source,
164+
Strings.ObfuscateLicenseKeyInAuditLog(uri, LicenseKeyParameterName));
165+
Logging.AuditLog.Log(message);
166+
}
163167
}
164168

165169
private Uri GetUri(string method, ConnectionInfo connectionInfo)

src/Agent/NewRelic/Agent/Core/Logging/AuditLog.cs

+23-7
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,43 @@ namespace NewRelic.Agent.Core.Logging
1010
public static class AuditLog
1111
{
1212
// a lazy ILogger instance that injects an "Audit" property
13-
private static Lazy<ILogger> _lazyAuditLogger = new Lazy<ILogger>(() =>
14-
Serilog.Log.Logger.ForContext(LogLevelExtensions.AuditLevel, LogLevelExtensions.AuditLevel));
13+
private static Lazy<ILogger> _lazyAuditLogger = LazyAuditLogger();
14+
15+
public static bool IsAuditLogEnabled { get; set; } //setter is public only for unit tests, not expected to be use anywhere else
16+
17+
// for unit tests only
18+
public static void ResetLazyLogger()
19+
{
20+
_lazyAuditLogger = LazyAuditLogger();
21+
}
22+
23+
private static Lazy<ILogger> LazyAuditLogger()
24+
{
25+
return new Lazy<ILogger>(() =>
26+
Serilog.Log.Logger.ForContext(LogLevelExtensions.AuditLevel, LogLevelExtensions.AuditLevel));
27+
}
1528

1629
/// <summary>
1730
/// Logs <paramref name="message"/> at the AUDIT level. This log level should be used only as dictated by the security team to satisfy auditing requirements.
1831
/// </summary>
1932
public static void Log(string message)
2033
{
34+
if (IsAuditLogEnabled)
2135
// use Fatal log level to ensure audit log messages never get filtered due to level restrictions
2236
_lazyAuditLogger.Value.Fatal(message);
2337
}
2438

25-
internal static LoggerConfiguration IncludeOnlyAuditLog(this LoggerConfiguration loggerConfiguration)
39+
public static LoggerConfiguration IncludeOnlyAuditLog(this LoggerConfiguration loggerConfiguration)
2640
{
27-
return loggerConfiguration.Filter.ByIncludingOnly($"{LogLevelExtensions.AuditLevel} is not null");
41+
IsAuditLogEnabled = true; // set a flag so Log() can short-circuit when audit log is not enabled
2842

29-
//return loggerConfiguration.Filter.ByIncludingOnly(logEvent =>
30-
// logEvent.Properties.ContainsKey(LogLevelExtensions.AuditLevel));
43+
return loggerConfiguration.Filter.ByIncludingOnly($"{LogLevelExtensions.AuditLevel} is not null");
3144
}
32-
internal static LoggerConfiguration ExcludeAuditLog(this LoggerConfiguration loggerConfiguration)
45+
46+
public static LoggerConfiguration ExcludeAuditLog(this LoggerConfiguration loggerConfiguration)
3347
{
48+
IsAuditLogEnabled = false; // set a flag so Log() can short-circuit when audit log is not enabled
49+
3450
return loggerConfiguration.Filter.ByIncludingOnly($"{LogLevelExtensions.AuditLevel} is null");
3551
}
3652
}

tests/Agent/UnitTests/Core.UnitTest/DataTransport/HttpCollectorWireTests.cs

+39
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
using System.Threading.Tasks;
1616
using System.Threading;
1717
using NewRelic.Agent.Core.Exceptions;
18+
using NewRelic.Agent.Core.Logging;
19+
using Serilog;
1820
using Telerik.JustMock;
1921

2022
namespace NewRelic.Agent.Core.DataTransport
@@ -26,13 +28,17 @@ public class HttpCollectorWireTests
2628
private IAgentHealthReporter _agentHealthReporter;
2729
private IHttpClientFactory _httpClientFactory;
2830
private MockHttpMessageHandler _mockHttpMessageHandler;
31+
private ILogger _mockILogger;
2932

3033
[SetUp]
3134
public void SetUp()
3235
{
3336
_configuration = Mock.Create<IConfiguration>();
3437
_agentHealthReporter = Mock.Create<IAgentHealthReporter>();
3538
_httpClientFactory = Mock.Create<IHttpClientFactory>();
39+
40+
_mockILogger = Mock.Create<ILogger>();
41+
Log.Logger = _mockILogger;
3642
}
3743

3844
private HttpCollectorWire CreateHttpCollectorWire(Dictionary<string, string> requestHeadersMap = null)
@@ -306,6 +312,39 @@ public void SendData_ShouldDropPayload_WhenPayloadSizeExceedsMaxSize()
306312
Mock.Assert(() => _httpClientFactory.CreateClient(Arg.IsAny<IWebProxy>()), Occurs.Never());
307313
Assert.AreEqual(false, _mockHttpMessageHandler.SendAsyncInvoked);
308314
}
315+
316+
[TestCase(false)]
317+
[TestCase(true)]
318+
public void SendData_ShouldNotCallAuditLog_UnlessAuditLogIsEnabled(bool isEnabled)
319+
{
320+
// Arrange
321+
AuditLog.ResetLazyLogger();
322+
Mock.Arrange(() => _configuration.AgentLicenseKey).Returns("license_key");
323+
Mock.Arrange(() => _configuration.CollectorMaxPayloadSizeInBytes).Returns(1024);
324+
325+
var connectionInfo = new ConnectionInfo(_configuration);
326+
var serializedData = "{ \"key\": \"value\" }";
327+
var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
328+
{
329+
Content = new StringContent("{}")
330+
};
331+
332+
CreateMockHttpClient(httpResponse, false);
333+
334+
var collectorWire = CreateHttpCollectorWire();
335+
336+
var mockForContextLogger = Mock.Create<ILogger>();
337+
Mock.Arrange(() => _mockILogger.ForContext(Arg.AnyString, Arg.AnyObject, false))
338+
.Returns(() => mockForContextLogger);
339+
340+
AuditLog.IsAuditLogEnabled = isEnabled;
341+
342+
// Act
343+
var _ = collectorWire.SendData("test_method", connectionInfo, serializedData, Guid.NewGuid());
344+
345+
// Assert
346+
Mock.Assert(() => mockForContextLogger.Fatal(Arg.AnyString), isEnabled ? Occurs.Exactly(3) : Occurs.Never());
347+
}
309348
}
310349

311350
public class MockHttpMessageHandler : HttpMessageHandler
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using NUnit.Framework;
5+
using Serilog;
6+
using Serilog.Configuration;
7+
using Telerik.JustMock;
8+
9+
namespace NewRelic.Agent.Core.Logging.Tests
10+
{
11+
[TestFixture]
12+
public class AuditLogTests
13+
{
14+
private ILogger _mockILogger;
15+
16+
[SetUp]
17+
public void SetUp()
18+
{
19+
_mockILogger = Mock.Create<ILogger>();
20+
Log.Logger = _mockILogger;
21+
22+
// reset state for each test
23+
AuditLog.ResetLazyLogger();
24+
AuditLog.IsAuditLogEnabled = false;
25+
}
26+
27+
[TearDown]
28+
public void TearDown()
29+
{
30+
AuditLog.ResetLazyLogger();
31+
}
32+
33+
[Test]
34+
public void IncludeOnlyAuditLog_EnablesAuditLog()
35+
{
36+
Assert.False(AuditLog.IsAuditLogEnabled);
37+
38+
var _ = new LoggerConfiguration().IncludeOnlyAuditLog();
39+
40+
Assert.True(AuditLog.IsAuditLogEnabled);
41+
}
42+
43+
[Test]
44+
public void ExcludeAuditLog_DisablesAuditLog()
45+
{
46+
AuditLog.IsAuditLogEnabled = true;
47+
48+
var _ = new LoggerConfiguration().ExcludeAuditLog();
49+
50+
Assert.False(AuditLog.IsAuditLogEnabled);
51+
}
52+
53+
[TestCase(true)]
54+
[TestCase(false)]
55+
public void Log_OnlyLogsWhenAuditLogEnabled(bool logEnabled)
56+
{
57+
var mockForContextLogger = Mock.Create<ILogger>();
58+
Mock.Arrange(() => _mockILogger.ForContext(Arg.AnyString, Arg.AnyObject, false))
59+
.Returns(() => mockForContextLogger);
60+
61+
AuditLog.IsAuditLogEnabled = logEnabled;
62+
63+
var message = "This is an audit message";
64+
65+
AuditLog.Log(message);
66+
67+
Mock.Assert(() => mockForContextLogger.Fatal(message), logEnabled ? Occurs.Once() : Occurs.Never());
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)