Skip to content

Commit 74699f8

Browse files
authored
Merge pull request #465 from elastic/feature/verify-server-cert
Add support for Server certificate verification
2 parents 2215227 + 7772842 commit 74699f8

12 files changed

+89
-1
lines changed

docs/configuration.asciidoc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,26 @@ This string is used to ensure that only your agents can send data to your APM se
393393
Both the agents and the APM server have to be configured with the same secret token.
394394
Use this setting in case the APM Server requires a token (e.g. APM Server in Elastic Cloud).
395395

396+
[float]
397+
[[config-verify-server-cert]]
398+
==== `VerifyServerCert` (added[1.3])
399+
400+
[options="header"]
401+
|============
402+
| Environment variable name | IConfiguration or Web.config key
403+
| `ELASTIC_APM_VERIFY_SERVER_CERT` | `ElasticApm:VerifyServerCert`
404+
|============
405+
406+
[options="header"]
407+
|============
408+
| Default | Type
409+
| `true` | Boolean
410+
|============
411+
412+
By default, the agent verifies the SSL certificate if you use an HTTPS connection to the APM server.
413+
414+
Verification can be disabled by changing this setting to false.
415+
396416
[float]
397417
[[config-flush-interval]]
398418
==== `FlushInterval` (added[1.1])

src/Elastic.Apm/BackendComm/BackendCommUtils.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
using System.Net;
44
using System.Net.Http;
55
using System.Net.Http.Headers;
6+
using System.Net.Security;
67
using System.Reflection;
8+
using System.Security.Cryptography.X509Certificates;
79
using System.Text;
810
using System.Text.RegularExpressions;
911
using Elastic.Apm.Api;
@@ -105,6 +107,18 @@ private static void ConfigServicePoint(Uri serverUrlBase, IApmLogger logger) =>
105107
servicePoint.ConnectionLimit = 20;
106108
});
107109

110+
private static HttpClientHandler CreateHttpClientHandler(bool verifyServerCert, IApmLogger logger)
111+
{
112+
bool ServerCertificateCustomValidationCallback(HttpRequestMessage message, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors policyError)
113+
{
114+
if (policyError == SslPolicyErrors.None) return true;
115+
116+
logger.Trace()?.Log("Certificate validation failed. Policy error {PolicyError}", policyError);
117+
return !verifyServerCert;
118+
}
119+
120+
return new HttpClientHandler { ServerCertificateCustomValidationCallback = ServerCertificateCustomValidationCallback };
121+
}
108122

109123
internal static HttpClient BuildHttpClient(IApmLogger loggerArg, IConfigSnapshot config, Service service, string dbgCallerDesc
110124
, HttpMessageHandler httpMessageHandler = null
@@ -118,7 +132,8 @@ internal static HttpClient BuildHttpClient(IApmLogger loggerArg, IConfigSnapshot
118132
logger.Debug()
119133
?.Log("Building HTTP client with BaseAddress: {ApmServerUrl} for {dbgCallerDesc}..."
120134
, serverUrlBase, dbgCallerDesc);
121-
var httpClient = new HttpClient(httpMessageHandler ?? new HttpClientHandler()) { BaseAddress = serverUrlBase };
135+
var httpClient =
136+
new HttpClient(httpMessageHandler ?? CreateHttpClientHandler(config.VerifyServerCert, loggerArg)) { BaseAddress = serverUrlBase };
122137
httpClient.DefaultRequestHeaders.UserAgent.Add(
123138
new ProductInfoHeaderValue($"elasticapm-{Consts.AgentName}", AdaptUserAgentValue(service.Agent.Version)));
124139
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("System.Net.Http",

src/Elastic.Apm/BackendComm/CentralConfigFetcher.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,10 @@ internal WrappingConfigSnapshot(IConfigSnapshot wrapped, ConfigDelta configDelta
475475
public int TransactionMaxSpans => _configDelta.TransactionMaxSpans ?? _wrapped.TransactionMaxSpans;
476476

477477
public double TransactionSampleRate => _configDelta.TransactionSampleRate ?? _wrapped.TransactionSampleRate;
478+
478479
public bool UseElasticTraceparentHeader => _wrapped.UseElasticTraceparentHeader;
480+
481+
public bool VerifyServerCert => _wrapped.VerifyServerCert;
479482
}
480483
}
481484
}

src/Elastic.Apm/Config/AbstractConfigurationReader.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ protected string ParseSecretToken(ConfigurationKeyValue kv)
120120
return kv.Value;
121121
}
122122

123+
protected bool ParseVerifyServerCert(ConfigurationKeyValue kv)
124+
{
125+
if (kv == null || string.IsNullOrEmpty(kv.Value)) return DefaultValues.VerifyServerCert;
126+
// ReSharper disable once SimplifyConditionalTernaryExpression
127+
return bool.TryParse(kv.Value, out var value) ? value : DefaultValues.VerifyServerCert;
128+
}
129+
123130
protected bool ParseCaptureHeaders(ConfigurationKeyValue kv) => ParseBoolOption(kv, DefaultValues.CaptureHeaders, "CaptureHeaders");
124131

125132
protected LogLevel ParseLogLevel(ConfigurationKeyValue kv)

src/Elastic.Apm/Config/AbstractConfigurationWithEnvFallbackReader.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,8 @@ internal AbstractConfigurationWithEnvFallbackReader(IApmLogger logger, string de
8787

8888
public bool UseElasticTraceparentHeader => ParseUseElasticTraceparentHeader(Read(ConfigConsts.KeyNames.UseElasticTraceparentheader,
8989
ConfigConsts.EnvVarNames.UseElasticTraceparentHeader));
90+
91+
public virtual bool VerifyServerCert =>
92+
ParseVerifyServerCert(Read(ConfigConsts.KeyNames.VerifyServerCert, ConfigConsts.EnvVarNames.VerifyServerCert));
9093
}
9194
}

src/Elastic.Apm/Config/ConfigConsts.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public static class DefaultValues
3030
public const double TransactionSampleRate = 1.0;
3131
public const string UnknownServiceName = "unknown";
3232
public const bool UseElasticTraceparentHeader = true;
33+
public const bool VerifyServerCert = true;
3334

3435
public static List<WildcardMatcher> DisableMetrics = new List<WildcardMatcher>();
3536

@@ -84,6 +85,7 @@ public static class EnvVarNames
8485
public const string TransactionMaxSpans = Prefix + "TRANSACTION_MAX_SPANS";
8586
public const string TransactionSampleRate = Prefix + "TRANSACTION_SAMPLE_RATE";
8687
public const string UseElasticTraceparentHeader = Prefix + "USE_ELASTIC_TRACEPARENT_HEADER";
88+
public const string VerifyServerCert = Prefix + "VERIFY_SERVER_CERT";
8789
}
8890

8991
public static class KeyNames
@@ -111,6 +113,7 @@ public static class KeyNames
111113
public const string TransactionMaxSpans = "ElasticApm:TransactionMaxSpans";
112114
public const string TransactionSampleRate = "ElasticApm:TransactionSampleRate";
113115
public const string UseElasticTraceparentheader = "ElasticApm:UseElasticTraceparentHeder";
116+
public const string VerifyServerCert = "ElasticApm:VerifyServerCert";
114117
}
115118

116119
public static class SupportedValues

src/Elastic.Apm/Config/ConfigSnapshotFromReader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ internal ConfigSnapshotFromReader(IConfigurationReader content, string dbgDescri
4040
public int TransactionMaxSpans => _content.TransactionMaxSpans;
4141
public double TransactionSampleRate => _content.TransactionSampleRate;
4242
public bool UseElasticTraceparentHeader => _content.UseElasticTraceparentHeader;
43+
public bool VerifyServerCert => _content.VerifyServerCert;
4344
}
4445
}

src/Elastic.Apm/Config/EnvironmentConfigurationReader.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public EnvironmentConfigurationReader(IApmLogger logger = null) : base(logger, T
7070

7171
public bool UseElasticTraceparentHeader => ParseUseElasticTraceparentHeader(Read(ConfigConsts.EnvVarNames.UseElasticTraceparentHeader));
7272

73+
public bool VerifyServerCert => ParseVerifyServerCert(Read(ConfigConsts.EnvVarNames.VerifyServerCert));
74+
7375
private ConfigurationKeyValue Read(string key) =>
7476
new ConfigurationKeyValue(key, ReadEnvVarValue(key), Origin);
7577
}

src/Elastic.Apm/Config/IConfigurationReader.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,11 @@ public interface IConfigurationReader
142142
/// otherwise it'll use the official header name from w3c, which is "traceparewnt".
143143
/// </summary>
144144
bool UseElasticTraceparentHeader { get; }
145+
146+
/// <summary>
147+
/// The agent verifies the server's certificate if an HTTPS connection to the APM server is used.
148+
/// Verification can be disabled by setting to <c>false</c>.
149+
/// </summary>
150+
bool VerifyServerCert { get; }
145151
}
146152
}

test/Elastic.Apm.Tests/ConfigTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,8 @@ public void GlobalLabels_invalid_input_tests(string optionValue)
753753
);
754754
}
755755

756+
757+
756758
/// <summary>
757759
/// Disables CPU metrics and makes sure that remaining metrics are still captured
758760
/// </summary>
@@ -779,6 +781,23 @@ public void DisableMetrics_DisableCpuMetrics()
779781
.Contain(n => n.KeyValue.Key.Equals("system.process.memory.rss.bytes", StringComparison.InvariantCultureIgnoreCase));
780782
}
781783

784+
[Fact]
785+
public void DefaultVerifyServerCertIsTrue()
786+
{
787+
var agent = new ApmAgent(new TestAgentComponents());
788+
agent.ConfigurationReader.VerifyServerCert.Should().BeTrue();
789+
}
790+
791+
[Theory]
792+
[InlineData("false", false)]
793+
[InlineData("true", true)]
794+
[InlineData("nonsense value", true)]
795+
public void SetVerifyServerCert(string verifyServerCert, bool expected)
796+
{
797+
var agent = new ApmAgent(new TestAgentComponents(config: new MockConfigSnapshot(verifyServerCert: verifyServerCert)));
798+
agent.ConfigurationReader.VerifyServerCert.Should().Be(expected);
799+
}
800+
782801
private static double MetricsIntervalTestCommon(string configValue)
783802
{
784803
Environment.SetEnvironmentVariable(EnvVarNames.MetricsInterval, configValue);

0 commit comments

Comments
 (0)