Skip to content

Commit 19e8387

Browse files
authored
Improve logging and validation for license keys. (#2982)
1 parent 15f88c7 commit 19e8387

File tree

3 files changed

+84
-15
lines changed

3 files changed

+84
-15
lines changed

src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs

+53-8
Original file line numberDiff line numberDiff line change
@@ -210,21 +210,66 @@ public virtual string AgentLicenseKey
210210
{
211211
get
212212
{
213-
if (_agentLicenseKey != null)
214-
return _agentLicenseKey;
213+
_agentLicenseKey ??= TryGetLicenseKey();
214+
if (string.IsNullOrWhiteSpace(_agentLicenseKey) && !ServerlessModeEnabled)
215+
{
216+
TrySetAgentControlStatus(HealthCodes.LicenseKeyMissing);
217+
}
215218

216-
_agentLicenseKey = _configurationManagerStatic.GetAppSetting(Constants.AppSettingsLicenseKey)
217-
?? EnvironmentOverrides(_localConfiguration.service.licenseKey, "NEW_RELIC_LICENSE_KEY", "NEWRELIC_LICENSEKEY");
219+
return _agentLicenseKey;
220+
}
221+
}
218222

219-
_agentLicenseKey = _agentLicenseKey?.Trim();
223+
private string TryGetLicenseKey()
224+
{
225+
// same order as old process - appsettings > env var(a/b) > newrelic.config
226+
var candidateKeys = new Dictionary<string, string>
227+
{
228+
{ "AppSettings", _configurationManagerStatic.GetAppSetting(Constants.AppSettingsLicenseKey) },
229+
{ "EnvironmentVariable", _environment.GetEnvironmentVariableFromList("NEW_RELIC_LICENSE_KEY", "NEWRELIC_LICENSEKEY") },
230+
{ "newrelic.config", _localConfiguration.service.licenseKey }
231+
};
220232

221-
if (string.IsNullOrEmpty(_agentLicenseKey) && !ServerlessModeEnabled)
233+
foreach (var candidateKey in candidateKeys)
234+
{
235+
// Did we find a key?
236+
if (string.IsNullOrWhiteSpace(candidateKey.Value))
222237
{
223-
TrySetAgentControlStatus(HealthCodes.LicenseKeyMissing);
238+
continue;
224239
}
225240

226-
return _agentLicenseKey;
241+
// We found something, but is it a valid key?
242+
243+
// If the key is the default value from newrelic.config, we return the default value
244+
// AgentManager.AssertAgentEnabled() relies on this behavior and will throw an exception if the key is the default value
245+
if (candidateKey.Value.ToLower().Contains("license"))
246+
{
247+
// newrelic.config is the last place to look
248+
return candidateKey.Value;
249+
}
250+
251+
// Keys are only 40 characters long, but we trim to be safe
252+
var trimmedCandidateKey = candidateKey.Value.Trim();
253+
if (trimmedCandidateKey.Length != 40)
254+
{
255+
Log.Warn($"License key from {candidateKey.Key} is not 40 characters long. Checking for other license keys.");
256+
continue;
257+
}
258+
259+
// Keys can only contain printable ASCII characters from 0x21 to 0x7E
260+
if (!trimmedCandidateKey.All(c => c >= 0x21 && c <= 0x7E))
261+
{
262+
Log.Warn($"License key from {candidateKey.Key} contains invalid characters. Checking for other license keys.");
263+
continue;
264+
}
265+
266+
Log.Info($"License key from {candidateKey.Key} appears to be in the correct format.");
267+
return trimmedCandidateKey;
227268
}
269+
270+
// return string.Empty instead of null to allow caching and prevent checking repeatedly
271+
Log.Warn("No valid license key found.");
272+
return string.Empty;
228273
}
229274

230275
private IEnumerable<string> _applicationNames;

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

+4
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ private void HandleConnectionException(Exception ex)
155155
Log.Info("401 GONE response received from the collector.");
156156
shouldRestart = false;
157157
}
158+
else if (httpEx.StatusCode == HttpStatusCode.Unauthorized)
159+
{
160+
Log.Warn("Connection failed: Potential issue with license key based on HTTP status code 401 - Unauthorized.\n\tPlease verify the license key.");
161+
}
158162
else
159163
Log.Info("Connection failed: {0}", ex.Message);
160164
break;

tests/Agent/UnitTests/Core.UnitTest/Configuration/DefaultConfigurationTests.cs

+27-7
Original file line numberDiff line numberDiff line change
@@ -1449,12 +1449,32 @@ public string CustomHostEnvironmentOverridesLocal(string environment, string loc
14491449
return _defaultConfig.CollectorHost;
14501450
}
14511451

1452-
[TestCase(null, null, null, ExpectedResult = null)]
1453-
[TestCase(null, "foo", null, ExpectedResult = "foo")]
1454-
[TestCase("foo", null, null, ExpectedResult = "foo")]
1455-
[TestCase("foo", null, "foo", ExpectedResult = "foo")]
1456-
[TestCase("foo", "foo", null, ExpectedResult = "foo")]
1457-
[TestCase("foo", "foo", "bar", ExpectedResult = "foo")]
1452+
// all null returns empty string
1453+
[TestCase(null, null, null, ExpectedResult = "")]
1454+
// AppSetting overrides environment and local
1455+
[TestCase("foo1234567890abcdefghijklmnopqrstuvwxyz0", null, null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1456+
[TestCase("foo1234567890abcdefghijklmnopqrstuvwxyz0", null, "bar1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1457+
[TestCase("foo1234567890abcdefghijklmnopqrstuvwxyz0", "bar1234567890abcdefghijklmnopqrstuvwxyz0", null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1458+
[TestCase("foo1234567890abcdefghijklmnopqrstuvwxyz0", "bar1234567890abcdefghijklmnopqrstuvwxyz0", "nar1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1459+
// Environment overrides local
1460+
[TestCase(null, "foo1234567890abcdefghijklmnopqrstuvwxyz0", null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1461+
[TestCase(null, "foo1234567890abcdefghijklmnopqrstuvwxyz0", "bar1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1462+
// local on its own
1463+
[TestCase(null, null, "foo1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1464+
[TestCase(null, null, "REPLACE_WITH_LICENSE_KEY", ExpectedResult = "REPLACE_WITH_LICENSE_KEY")]
1465+
// Length must be 40
1466+
[TestCase(" foo1234567890abcdefghijklmnopqrstuvwxyz0 ", null, null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1467+
[TestCase("foo1234567890abcdefghijklmnopqrstuvwxyz0123456789", null, null, ExpectedResult = "")]
1468+
[TestCase("foo", null, null, ExpectedResult = "")]
1469+
// Allowed characters
1470+
[TestCase("foo1234567890abcdefghijklmnopqrstuvyz\tzz", null, null, ExpectedResult = "")]
1471+
// Bad keys skipped for lower priority keys
1472+
[TestCase("foo1234567890abcdefghijklmnopqrstuvwxyz0123456789", "foo1234567890abcdefghijklmnopqrstuvwxyz0", null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1473+
[TestCase(null, "foo1234567890abcdefghijklmnopqrstuvwxyz0123456789", "foo1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1474+
[TestCase("foo", "foo1234567890abcdefghijklmnopqrstuvwxyz0", null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1475+
[TestCase(null, "foo", "foo1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1476+
[TestCase("foo1234567890abcdefghijklmnopqrstuvyz\tzz", "foo1234567890abcdefghijklmnopqrstuvwxyz0", null, ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
1477+
[TestCase(null, "foo1234567890abcdefghijklmnopqrstuvyz\tzz", "foo1234567890abcdefghijklmnopqrstuvwxyz0", ExpectedResult = "foo1234567890abcdefghijklmnopqrstuvwxyz0")]
14581478
public string LicenseKeyEnvironmentOverridesLocal(string appSettingEnvironmentName, string newEnvironmentName, string local)
14591479
{
14601480
_localConfig.service.licenseKey = local;
@@ -4533,7 +4553,7 @@ public void InvalidLicenseKey_SetsLicenseKeyMissing_AgentControlStatus()
45334553
// Assert
45344554
Assert.Multiple(() =>
45354555
{
4536-
Assert.That(licenseKey, Is.EqualTo(""));
4556+
Assert.That(licenseKey, Is.EqualTo(string.Empty));
45374557
Assert.That(healthCheck.IsHealthy, Is.False);
45384558
Assert.That(healthCheck.Status, Is.EqualTo("License key missing in configuration"));
45394559
Assert.That(healthCheck.LastError, Is.EqualTo("NR-APM-002"));

0 commit comments

Comments
 (0)