From e04dd4d12860cf189f0c964f5d46f789b6de79b0 Mon Sep 17 00:00:00 2001 From: Mpdreamz Date: Fri, 17 Feb 2017 13:27:03 +0100 Subject: [PATCH 1/6] Make it easier to work with server and client certificates in the client ### Server certificates Rather then making folks register their ServerCertificateValidation callback globally on the static `ServicePointManager` or subclass `HttpConnection` to set it on the request/itself we now expose it on `ConnectionSettings` this callback is fired for each unique endpoint (node) until it returns true after which its cached for the duration of that servicepoint. We also ship with handy baked in validations on `CertificateValidations`: * `CertificateValidations.AllowAll` simply returns true * `CertificateValidations.DenyAll` simply returns false If your client application however has access to the public CA certificate locally Elasticsearch.NET/NEST ships with handy helpers that assert that the certificate that the server presented was one that came from our local CA certificate. If you use x-pack's `certgen` tool to [generate SSL certificates](https://www.elastic.co/guide/en/x-pack/current/ssl-tls.html) the generated node certificate does not include the CA in the certificate chain. This to cut back on SSL handshake size. In those case you can use `CertificateValidations.AuthorityIsRoot` and pass it your local copy of the CA public key to assert that the certificate the server presented was generated off that. If you go for a vendor generated SSL certificate its common practice for them to include the CA and any intermediary CA's in the certificate chain in those case use `CertificateValidations.AuthorityPartOfChain` which validates that the local CA certificate is part of that chain and was used to generate the servers key. ### Client certificates `ConnectionSettings` now also accepts `ClientCertificates` as a collection or `ClientCertificate` as a single certificate to be used as the user authentication for ALL requests. `RequestConfiguration` accepts the same but will be the client certificate for that single request only. The client certificate should be a certificate that has the public and private key available (`pfx` or `p12`) however x-pack `certgen` generates two separate `cer` and `key` files. For .NET 4.5/4.6 we ship with a helper that creates a proper self contained certificate from these two files `ClientCertificate.LoadWithPrivateKey` but because we can no longer update a certificates `PublicKey` algorithm in .NET core this is not available there. Its typically recommended to generate a single pfx or p12 file since those can just be passed to `X509Certificate`'s constructor --- .../Configuration/ConnectionConfiguration.cs | 37 ++++ .../IConnectionConfigurationValues.cs | 13 ++ .../Configuration/RequestConfiguration.cs | 27 +++ .../Connection/CertificateValidations.cs | 129 ++++++++++++ .../Connection/ClientCertificate.cs | 149 ++++++++++++++ .../Connection/HttpConnection-CoreFx.cs | 13 +- .../Connection/HttpConnection.cs | 28 ++- .../Elasticsearch.Net.csproj | 2 + .../Extensions/X509CertificateExtensions.cs | 43 ++++ .../Transport/Pipeline/RequestData.cs | 5 + src/Tests/App.config | 33 +++ .../Certificates/SslAndKpiXPackCluster.cs | 111 ++++++++++ .../WorkingWithCertificates.doc.cs | 191 ++++++++++++++++++ .../ApiIntegrationAgainstNewIndexTestBase.cs | 29 +++ .../EndpointTests/ApiIntegrationTestBase.cs | 32 +-- .../EndpointTests/CanConnectTestBase.cs | 34 ++++ .../EndpointTests/ConnectionErrorTestBase.cs | 47 +++++ .../Clusters/ClusterBase.cs | 15 +- .../Clusters/XPackCluster.cs | 9 +- .../Nodes/ElasticsearchNode.cs | 13 +- .../Nodes/NodeConfiguration.cs | 12 +- .../Nodes/NodeFileSystem.cs | 20 ++ .../InstallationTasks/EnsureSecurityRealms.cs | 56 +++++ .../Tasks/NodeTaskRunner.cs | 8 +- .../ValidateClusterStateTask.cs | 2 +- .../ValidationTasks/ValidateLicenseTask.cs | 9 +- .../ValidationTasks/ValidatePluginsTask.cs | 3 +- .../ValidationTasks/ValidateRunningVersion.cs | 1 + src/Tests/Framework/TestClient.cs | 18 +- .../Framework/Xunit/TestAssemblyRunner.cs | 1 + src/Tests/Tests.csproj | 7 + 31 files changed, 1040 insertions(+), 57 deletions(-) create mode 100644 src/Elasticsearch.Net/Connection/CertificateValidations.cs create mode 100644 src/Elasticsearch.Net/Connection/ClientCertificate.cs create mode 100644 src/Elasticsearch.Net/Extensions/X509CertificateExtensions.cs create mode 100644 src/Tests/App.config create mode 100644 src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs create mode 100644 src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs create mode 100644 src/Tests/Framework/EndpointTests/ApiIntegrationAgainstNewIndexTestBase.cs create mode 100644 src/Tests/Framework/EndpointTests/CanConnectTestBase.cs create mode 100644 src/Tests/Framework/EndpointTests/ConnectionErrorTestBase.cs create mode 100644 src/Tests/Framework/ManagedElasticsearch/Tasks/InstallationTasks/EnsureSecurityRealms.cs diff --git a/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs b/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs index 09f42bc78a2..c1f17721b0d 100644 --- a/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs +++ b/src/Elasticsearch.Net/Configuration/ConnectionConfiguration.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; using System.Threading; #if DOTNETCORE @@ -143,6 +145,12 @@ private static void DefaultRequestDataCreated(RequestData response) { } private Action _onRequestDataCreated = DefaultRequestDataCreated; Action IConnectionConfigurationValues.OnRequestDataCreated => _onRequestDataCreated; + private Func _serverCertificateValidationCallback; + Func IConnectionConfigurationValues.ServerCertificateValidationCallback => _serverCertificateValidationCallback; + + private X509CertificateCollection _clientCertificates; + X509CertificateCollection IConnectionConfigurationValues.ClientCertificates => _clientCertificates; + /// /// The default predicate for implementations that return true for /// in which case master only nodes are excluded from API calls. @@ -412,6 +420,35 @@ public T EnableDebugMode(Action onRequestCompleted = null) return (T)this; } + /// + /// Register a ServerCertificateValidationCallback, this is called per endpoint until it returns true. + /// After this callback returns true that endpoint is validated for the lifetime of the ServiceEndpoint + /// for that host. + /// + public T ServerCertificateValidationCallback(Func callback) => + Assign(a => a._serverCertificateValidationCallback = callback); + + /// + /// Use the following certificates to authenticate all HTTP requests. You can also set them on individual + /// request using + /// + public T ClientCertificates(X509CertificateCollection certificates) => + Assign(a => a._clientCertificates = certificates); + + /// + /// Use the following certificate to authenticate all HTTP requests. You can also set them on individual + /// request using + /// + public T ClientCertificate(X509Certificate certificate) => + Assign(a => a._clientCertificates = new X509Certificate2Collection { certificate }); + + /// + /// Use the following certificate to authenticate all HTTP requests. You can also set them on individual + /// request using + /// + public T ClientCertificate(string certificatePath) => + Assign(a => a._clientCertificates = new X509Certificate2Collection { new X509Certificate(certificatePath) }); + void IDisposable.Dispose() => this.DisposeManagedResources(); protected virtual void DisposeManagedResources() diff --git a/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs b/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs index 13d9afbb639..9322de98ba3 100644 --- a/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs +++ b/src/Elasticsearch.Net/Configuration/IConnectionConfigurationValues.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Specialized; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; using System.Threading; namespace Elasticsearch.Net @@ -172,11 +174,22 @@ public interface IConnectionConfigurationValues : IDisposable /// TimeSpan? KeepAliveInterval { get; } + /// + /// Register a ServerCertificateValidationCallback per request + /// + Func ServerCertificateValidationCallback { get; } + /// /// Register a predicate to select which nodes that you want to execute API calls on. Note that sniffing requests omit this predicate and always execute on all nodes. /// When using an implementation that supports reseeding of nodes, this will default to omitting master only node from regular API calls. /// When using static or single node connection pooling it is assumed the list of node you instantiate the client with should be taken verbatim. /// Func NodePredicate { get; } + + /// + /// Use the following certificates to authenticate all HTTP requests. You can also set them on individual + /// request using + /// + X509CertificateCollection ClientCertificates { get; } } } diff --git a/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs b/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs index 62d848d996c..b589784e29d 100644 --- a/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs +++ b/src/Elasticsearch.Net/Configuration/RequestConfiguration.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; using System.Threading; namespace Elasticsearch.Net @@ -74,6 +76,11 @@ public interface IRequestConfiguration ///
https://www.elastic.co/guide/en/shield/current/submitting-requests-for-other-users.html
 		/// 
 		string RunAs { get; set; }
+
+		/// 
+		/// Use the following client certificates to authenticate this single request
+		/// 
+		X509CertificateCollection ClientCertificates { get; set; }
 	}
 
 	public class RequestConfiguration : IRequestConfiguration
@@ -96,6 +103,8 @@ public class RequestConfiguration : IRequestConfiguration
 		/// https://www.elastic.co/guide/en/shield/current/submitting-requests-for-other-users.html
 		/// 
 		public string RunAs { get; set; }
+
+		public X509CertificateCollection ClientCertificates { get; set; }
 	}
 
 	public class RequestConfigurationDescriptor : IRequestConfiguration
@@ -115,6 +124,7 @@ public class RequestConfigurationDescriptor : IRequestConfiguration
 		BasicAuthenticationCredentials IRequestConfiguration.BasicAuthenticationCredentials { get; set; }
 		bool IRequestConfiguration.EnableHttpPipelining { get; set; } = true;
 		string IRequestConfiguration.RunAs { get; set; }
+		X509CertificateCollection IRequestConfiguration.ClientCertificates { get; set; }
 
 		public RequestConfigurationDescriptor(IRequestConfiguration config)
 		{
@@ -131,6 +141,7 @@ public RequestConfigurationDescriptor(IRequestConfiguration config)
 			Self.BasicAuthenticationCredentials = config?.BasicAuthenticationCredentials;
 			Self.EnableHttpPipelining = config?.EnableHttpPipelining ?? true;
 			Self.RunAs = config?.RunAs;
+			Self.ClientCertificates = config?.ClientCertificates;
 		}
 
 		/// 
@@ -202,6 +213,7 @@ public RequestConfigurationDescriptor ForceNode(Uri uri)
 			Self.ForceNode = uri;
 			return this;
 		}
+
 		public RequestConfigurationDescriptor MaxRetries(int retry)
 		{
 			Self.MaxRetries = retry;
@@ -222,5 +234,20 @@ public RequestConfigurationDescriptor EnableHttpPipelining(bool enable = true)
 			Self.EnableHttpPipelining = enable;
 			return this;
 		}
+
+		///  Use the following client certificates to authenticate this request to Elasticsearch 
+		public RequestConfigurationDescriptor ClientCertificates(X509CertificateCollection certificates)
+		{
+			Self.ClientCertificates = certificates;
+			return this;
+		}
+
+		///  Use the following client certificate to authenticate this request to Elasticsearch 
+		public RequestConfigurationDescriptor ClientCertificate(X509Certificate certificate) =>
+			this.ClientCertificates(new X509Certificate2Collection { certificate });
+
+		///  Use the following client certificate to authenticate this request to Elasticsearch 
+		public RequestConfigurationDescriptor ClientCertificate(string certificatePath) =>
+			this.ClientCertificates(new X509Certificate2Collection {new X509Certificate(certificatePath)});
 	}
 }
diff --git a/src/Elasticsearch.Net/Connection/CertificateValidations.cs b/src/Elasticsearch.Net/Connection/CertificateValidations.cs
new file mode 100644
index 00000000000..0a6038dcbd3
--- /dev/null
+++ b/src/Elasticsearch.Net/Connection/CertificateValidations.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Elasticsearch.Net
+{
+	/// 
+	/// A collection of handy baked in server certificate validation callbacks
+	/// 
+	public static class CertificateValidations
+	{
+		/// 
+		/// DANGEROUS, never use this in production validates ALL certificates to true.
+		/// 
+		/// Always true, allowing ALL certificates
+		public static bool AllowAll(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) => true;
+
+		/// 
+		/// Always false, in effect blocking ALL certificates
+		/// 
+		/// Always false, always blocking ALL certificates
+		public static bool DenyAll(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) => false;
+
+		/// 
+		/// Helper to create a certificate validation callback based on the certificate authority certificate that we used to
+		/// generate the nodes certificates with. This callback expects the CA to be part of the chain as intermediate CA.
+		/// 
+		/// The ca certificate used to generate the nodes certificate 
+		/// Custom CA are never trusted by default unless they are in the machines trusted store, set this to true
+		/// if you've added the CA to the machines trusted store. In which case UntrustedRoot should not be accepted.
+		/// 
+		/// By default we do not check revocation, it is however recommended to check this (either offline or online).
+		public static Func AuthorityPartOfChain(
+			X509Certificate caCertificate, bool trustRoot = true, X509RevocationMode revocationMode = X509RevocationMode.NoCheck) =>
+			(sender, cert, chain, errors) =>
+				errors == SslPolicyErrors.None
+				|| ValidIntermediateCa(caCertificate, cert, chain, trustRoot, revocationMode);
+
+		/// 
+		/// Helper to create a certificate validation callback based on the certificate authority certificate that we used to
+		/// generate the nodes certificates with. This callback does NOT expect the CA to be part of the chain presented by the server.
+		/// Including the root certificate in the chain increases the SSL handshake size and Elasticsearch's certgen by default does not include
+		/// the CA in the certificate chain.
+		/// 
+		/// The ca certificate used to generate the nodes certificate 
+		/// Custom CA are never trusted by default unless they are in the machines trusted store, set this to true
+		/// if you've added the CA to the machines trusted store. In which case UntrustedRoot should not be accepted.
+		/// 
+		/// By default we do not check revocation, it is however recommended to check this (either offline or online).
+		public static Func AuthorityIsRoot(
+			X509Certificate caCertificate, bool trustRoot = true, X509RevocationMode revocationMode = X509RevocationMode.NoCheck) =>
+			(sender, cert, chain, errors) =>
+				errors == SslPolicyErrors.None
+				|| ValidRootCa(caCertificate, cert, chain, trustRoot, revocationMode);
+
+		private static X509Certificate2 to2(X509Certificate certificate)
+		{
+			#if DOTNETCORE
+				return new X509Certificate2(certificate.Export(X509ContentType.Cert));
+			#else
+				return new X509Certificate2(certificate);
+			#endif
+		}
+
+		private static bool ValidRootCa(X509Certificate caCertificate, X509Certificate certificate, X509Chain chain, bool trustRoot,
+			X509RevocationMode revocationMode)
+		{
+			var ca = to2(caCertificate);
+			var privateChain = new X509Chain {ChainPolicy = {RevocationMode = revocationMode}};
+			privateChain.ChainPolicy.ExtraStore.Add(ca);
+			privateChain.Build(to2(certificate));
+
+			//lets validate the our chain status
+			foreach (var chainStatus in privateChain.ChainStatus)
+			{
+				//custom CA's that are not in the machine trusted store will always have this status
+				//by setting trustRoot = true (default) we skip this error
+				if (chainStatus.Status == X509ChainStatusFlags.UntrustedRoot && trustRoot) continue;
+				//trustRoot is false so we expected our CA to be in the machines trusted store
+				if (chainStatus.Status == X509ChainStatusFlags.UntrustedRoot) return false;
+				//otherwise if the chain has any error of any sort return false
+				if (chainStatus.Status != X509ChainStatusFlags.NoError) return false;
+			}
+			return true;
+
+		}
+
+		private static bool ValidIntermediateCa(X509Certificate caCertificate, X509Certificate certificate, X509Chain chain, bool trustRoot,
+			X509RevocationMode revocationMode)
+		{
+			var ca = to2(caCertificate);
+			var privateChain = new X509Chain {ChainPolicy = {RevocationMode = revocationMode}};
+			privateChain.ChainPolicy.ExtraStore.Add(ca);
+			privateChain.Build(to2(certificate));
+
+			//Assert our chain has the same number of elements as the certifcate presented by the server
+			if (chain.ChainElements.Count != privateChain.ChainElements.Count) return false;
+
+			//lets validate the our chain status
+			foreach (var chainStatus in privateChain.ChainStatus)
+			{
+				//custom CA's that are not in the machine trusted store will always have this status
+				//by setting trustRoot = true (default) we skip this error
+				if (chainStatus.Status == X509ChainStatusFlags.UntrustedRoot && trustRoot) continue;
+				//trustRoot is false so we expected our CA to be in the machines trusted store
+				if (chainStatus.Status == X509ChainStatusFlags.UntrustedRoot) return false;
+				//otherwise if the chain has any error of any sort return false
+				if (chainStatus.Status != X509ChainStatusFlags.NoError) return false;
+			}
+
+			var found = false;
+			//We are going to walk both chains and make sure the thumbprints align
+			//while making sure one of the chains certificates presented by the server has our expected CA thumbprint
+			for (var i = 0; i < chain.ChainElements.Count; i++)
+			{
+				var c = chain.ChainElements[i].Certificate.Thumbprint;
+				var cPrivate = privateChain.ChainElements[i].Certificate.Thumbprint;
+				if (c == ca.Thumbprint) found = true;
+
+				//mis aligned certificate chain, return false so we do not accept this certificate
+				if (c != cPrivate) return false;
+				i++;
+			}
+			return found;
+		}
+	}
+}
diff --git a/src/Elasticsearch.Net/Connection/ClientCertificate.cs b/src/Elasticsearch.Net/Connection/ClientCertificate.cs
new file mode 100644
index 00000000000..76a60cb43d2
--- /dev/null
+++ b/src/Elasticsearch.Net/Connection/ClientCertificate.cs
@@ -0,0 +1,149 @@
+// this code contains a refactored version of DecodeRSAPrivateKey() found here http://www.jensign.com/opensslkey/opensslkey.cs
+// Its license permits redistribution, the license is included here for reference.
+
+/*
+//
+//OpenSSLKey
+// .NET 2.0  OpenSSL Public & Private Key Parser
+//
+/*
+Copyright (c) 2000  JavaScience Consulting,  Michel Gallant
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Elasticsearch.Net
+{
+//.NET core does not allow to set PrivateKey, you'll have to manually convert to pfx/p12 or add the key to the machine store
+#if !DOTNETCORE
+
+	public class ClientCertificate
+	{
+		//https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem
+		public static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] privkey)
+		{
+			// ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
+			using (var mem = new MemoryStream(privkey))
+			using (var binr = new BinaryReader(mem))
+			{
+				var twobytes = binr.ReadUInt16();
+				switch (twobytes)
+				{
+					case 0x8130:
+						binr.ReadByte(); //advance 1 byte
+						break;
+					case 0x8230:
+						binr.ReadInt16(); //advance 2 bytes
+						break;
+					default:
+						return null;
+				}
+
+				twobytes = binr.ReadUInt16();
+				if (twobytes != 0x0102) return null; //version number
+				var bt = binr.ReadByte();
+				if (bt != 0x00) return null;
+
+				// We make sure the provider typeString is compatible with RSA
+				// ----
+				// https://msdn.microsoft.com/en-us/library/system.security.cryptography.cspparameters.providertype(v=vs.110).aspx
+				// https://msdn.microsoft.com/en-us/subscriptions/aa387431.aspx
+				// https://blogs.msdn.microsoft.com/alejacma/2009/04/30/default-provider-typeString-for-cspparameters-has-changed/
+
+				var serviceProvider = new RSACryptoServiceProvider(new CspParameters
+				{
+					Flags = CspProviderFlags.NoFlags,
+					KeyContainerName = Guid.NewGuid().ToString(),
+					ProviderType = 1
+				});
+				serviceProvider.ImportParameters(new RSAParameters
+				{
+					Modulus = ReadNext(binr),
+					Exponent = ReadNext(binr),
+					D = ReadNext(binr),
+					P = ReadNext(binr),
+					Q = ReadNext(binr),
+					DP = ReadNext(binr),
+					DQ = ReadNext(binr),
+					InverseQ = ReadNext(binr)
+				});
+				return serviceProvider;
+			}
+		}
+
+		private static byte[] ReadNext(BinaryReader br) => br.ReadBytes(GetSizeOfIntegerToReadNext(br));
+
+		private static int GetSizeOfIntegerToReadNext(BinaryReader br)
+		{
+			var bt = br.ReadByte();
+			if (bt != 0x02) return 0; //expect integer
+
+			var count = 0;
+			bt = br.ReadByte();
+			switch (bt)
+			{
+				case 0x81:
+					count = br.ReadByte(); // data size in next byte
+					break;
+				case 0x82:
+					var highbyte = br.ReadByte();
+					var lowbyte = br.ReadByte();
+					byte[] modint = {lowbyte, highbyte, 0x00, 0x00};
+					count = BitConverter.ToInt32(modint, 0);
+					break;
+				default:
+					count = bt; // we already have the data size
+					break;
+			}
+			while (br.ReadByte() == 0x00) //remove high order zeros in data
+				count -= 1;
+			br.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
+			return count;
+		}
+
+		private static byte[] ReadBytesFromPemFile(string fileContents, string typeString)
+		{
+			var header = $"-----BEGIN {typeString}-----";
+			var footer = $"-----END {typeString}-----";
+			var start = fileContents.IndexOf(header, StringComparison.Ordinal) + header.Length;
+			var end = fileContents.IndexOf(footer, start, StringComparison.Ordinal) - start;
+			var base64Der = fileContents.Substring(start, end);
+			return Convert.FromBase64String(base64Der);
+		}
+
+		public static X509Certificate2 LoadWithPrivateKey(string publicCertificatePath, string privateKeyPath, string password)
+		{
+			var publicCert = File.ReadAllText(publicCertificatePath);
+			var privateKey = File.ReadAllText(privateKeyPath);
+			var certBuffer = ReadBytesFromPemFile(publicCert, "CERTIFICATE");
+			var keyBuffer = ReadBytesFromPemFile(privateKey, "RSA PRIVATE KEY");
+			var certificate = !string.IsNullOrEmpty(password) ? new X509Certificate2(certBuffer, password) : new X509Certificate2(certBuffer);
+			var prov = DecodeRsaPrivateKey(keyBuffer);
+			certificate.PrivateKey = prov;
+			return certificate;
+		}
+	}
+#endif
+}
diff --git a/src/Elasticsearch.Net/Connection/HttpConnection-CoreFx.cs b/src/Elasticsearch.Net/Connection/HttpConnection-CoreFx.cs
index a9011b0379b..b6766900526 100644
--- a/src/Elasticsearch.Net/Connection/HttpConnection-CoreFx.cs
+++ b/src/Elasticsearch.Net/Connection/HttpConnection-CoreFx.cs
@@ -110,7 +110,7 @@ public virtual async Task> RequestAsync(
 			return await builder.ToResponseAsync().ConfigureAwait(false);
 		}
 
-		private static string MissingConnectionLimitMethodError =
+		private static readonly string MissingConnectionLimitMethodError =
 			$"Your target platform does not support {nameof(ConnectionConfiguration.ConnectionLimit)}"
 			+ $" please set {nameof(ConnectionConfiguration.ConnectionLimit)} to -1 on your connection configuration."
 			+ $" this will cause the {nameof(HttpClientHandler.MaxConnectionsPerServer)} not to be set on {nameof(HttpClientHandler)}";
@@ -147,6 +147,17 @@ protected virtual HttpClientHandler CreateHttpClientHandler(RequestData requestD
 			if (requestData.DisableAutomaticProxyDetection)
 				handler.Proxy = null;
 
+			var callback = requestData?.ConnectionSettings?.ServerCertificateValidationCallback;
+			if (callback != null && handler.ServerCertificateCustomValidationCallback == null)
+				handler.ServerCertificateCustomValidationCallback = callback;
+
+
+			if (requestData.ClientCertificates != null)
+			{
+				handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
+				handler.ClientCertificates.AddRange(requestData.ClientCertificates);
+			}
+
 			return handler;
 		}
 
diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs
index 69ff9e5995f..c8fecdaff75 100644
--- a/src/Elasticsearch.Net/Connection/HttpConnection.cs
+++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs
@@ -3,6 +3,7 @@
 using System.IO.Compression;
 using System.Linq;
 using System.Net;
+using System.Net.Security;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
@@ -15,15 +16,12 @@ public class HttpConnection : IConnection
 	{
 		static HttpConnection()
 		{
-			//ServicePointManager.SetTcpKeepAlive(true, 2000, 2000);
-
-			//WebException's GetResponse is limitted to 65kb by default.
-			//Elasticsearch can be alot more chatty then that when dumping exceptions
-			//On error responses, so lets up the ante.
-
 			//Not available under mono
 			if (Type.GetType("Mono.Runtime") == null)
 				HttpWebRequest.DefaultMaximumErrorResponseLength = -1;
+
+			ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
+				SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
 		}
 
 		protected virtual HttpWebRequest CreateHttpWebRequest(RequestData requestData)
@@ -31,10 +29,27 @@ protected virtual HttpWebRequest CreateHttpWebRequest(RequestData requestData)
 			var request = this.CreateWebRequest(requestData);
 			this.SetBasicAuthenticationIfNeeded(request, requestData);
 			this.SetProxyIfNeeded(request, requestData);
+			this.SetServerCertificateValidationCallBackIfNeeded(request, requestData);
+			this.SetClientCertificates(request, requestData);
 			this.AlterServicePoint(request.ServicePoint, requestData);
 			return request;
 		}
 
+		protected virtual void SetClientCertificates(HttpWebRequest request, RequestData requestData)
+		{
+			if (requestData.ClientCertificates != null)
+				request.ClientCertificates.AddRange(requestData.ClientCertificates);
+		}
+
+
+		protected virtual void SetServerCertificateValidationCallBackIfNeeded(HttpWebRequest request, RequestData requestData)
+		{
+			//Only assign if one is defined on connection settings and a subclass has not already set one
+			var callback = requestData?.ConnectionSettings?.ServerCertificateValidationCallback;
+			if (callback != null && request.ServerCertificateValidationCallback == null)
+				request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(callback);
+		}
+
 		protected virtual HttpWebRequest CreateWebRequest(RequestData requestData)
 		{
 			var request = (HttpWebRequest)WebRequest.Create(requestData.Uri);
@@ -83,6 +98,7 @@ protected virtual void AlterServicePoint(ServicePoint requestServicePoint, Reque
 			//this method only sets internal values and wont actually cause timers and such to be reset
 			//So it should be idempotent if called with the same parameters
 			requestServicePoint.SetTcpKeepAlive(true, requestData.KeepAliveTime, requestData.KeepAliveInterval);
+
 		}
 
 		protected virtual void SetProxyIfNeeded(HttpWebRequest request, RequestData requestData)
diff --git a/src/Elasticsearch.Net/Elasticsearch.Net.csproj b/src/Elasticsearch.Net/Elasticsearch.Net.csproj
index 05eecd78a27..1a41c1971fb 100644
--- a/src/Elasticsearch.Net/Elasticsearch.Net.csproj
+++ b/src/Elasticsearch.Net/Elasticsearch.Net.csproj
@@ -67,11 +67,13 @@
     
     
     
+    
     
     
     
     
     
+    
     
     
     
diff --git a/src/Elasticsearch.Net/Extensions/X509CertificateExtensions.cs b/src/Elasticsearch.Net/Extensions/X509CertificateExtensions.cs
new file mode 100644
index 00000000000..2a25c4c0d91
--- /dev/null
+++ b/src/Elasticsearch.Net/Extensions/X509CertificateExtensions.cs
@@ -0,0 +1,43 @@
+using System.Security.Cryptography.X509Certificates;
+
+namespace Elasticsearch.Net
+{
+	internal static class X509CertificateExtensions
+	{
+#if DOTNETCORE
+
+		// https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/x509certificates/x509certificate.cs,318
+		internal static string GetCertHashString(this X509Certificate certificate)
+		{
+			var bytes = certificate.GetCertHash();
+			return EncodeHexString(bytes);
+
+		}
+		// https://referencesource.microsoft.com/#mscorlib/system/security/util/hex.cs,1bfe838f662feef3
+
+		// converts number to hex digit. Does not do any range checks.
+		private static char HexDigit(int num) {
+			return (char)((num < 10) ? (num + '0') : (num + ('A' - 10)));
+		}
+
+		private static string EncodeHexString(byte[] sArray)
+		{
+			string result = null;
+
+			if (sArray == null) return result;
+			var hexOrder = new char[sArray.Length * 2];
+
+			for(int i = 0, j = 0; i < sArray.Length; i++) {
+				var digit = (sArray[i] & 0xf0) >> 4;
+				hexOrder[j++] = HexDigit(digit);
+				digit = sArray[i] & 0x0f;
+				hexOrder[j++] = HexDigit(digit);
+			}
+			result = new string(hexOrder);
+			return result;
+		}
+
+#endif
+
+	}
+}
diff --git a/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs b/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs
index 3a80a0fccdb..0c7d427f1d1 100644
--- a/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs
+++ b/src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs
@@ -3,6 +3,8 @@
 using System.Collections.Specialized;
 using System.IO;
 using System.Linq;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
 using Purify;
 
 namespace Elasticsearch.Net
@@ -45,6 +47,8 @@ public class RequestData
 		public IConnectionConfigurationValues ConnectionSettings { get; }
 		public IMemoryStreamFactory MemoryStreamFactory { get; }
 
+		public X509CertificateCollection ClientCertificates { get; set; }
+
 		public RequestData(HttpMethod method, string path, PostData data, IConnectionConfigurationValues global, IRequestParameters local, IMemoryStreamFactory memoryStreamFactory)
 			: this(method, path, data, global, local?.RequestConfiguration, memoryStreamFactory)
 		{
@@ -92,6 +96,7 @@ private RequestData(
 			this.DisableAutomaticProxyDetection = global.DisableAutomaticProxyDetection;
 			this.BasicAuthorizationCredentials = local?.BasicAuthenticationCredentials ?? global.BasicAuthenticationCredentials;
 			this.AllowedStatusCodes = local?.AllowedStatusCodes ?? Enumerable.Empty();
+			this.ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates;
 		}
 
 		private string CreatePathWithQueryStrings(string path, IConnectionConfigurationValues global, IRequestParameters request = null)
diff --git a/src/Tests/App.config b/src/Tests/App.config
new file mode 100644
index 00000000000..b6a695548bd
--- /dev/null
+++ b/src/Tests/App.config
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs b/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs
new file mode 100644
index 00000000000..d16e3899842
--- /dev/null
+++ b/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs
@@ -0,0 +1,111 @@
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using Nest;
+using Tests.Framework;
+using Tests.Framework.ManagedElasticsearch.Clusters;
+using Tests.Framework.ManagedElasticsearch.Nodes;
+using Tests.Framework.ManagedElasticsearch.Plugins;
+using Tests.Framework.ManagedElasticsearch.Tasks.InstallationTasks;
+
+namespace Tests.ClientConcepts.Certificates
+{
+	[RequiresPlugin(ElasticsearchPlugin.XPack)]
+	public abstract class SslAndKpiXPackCluster : XPackCluster
+	{
+		public override bool EnableSsl { get; } = true;
+		/// 
+		/// Skipping bootstrap validation because they call out to elasticsearch and would force
+		/// The ServerCertificateValidationCallback to return true. Since i
+		/// 
+		public override bool SkipValidation { get; } = true;
+
+		protected override InstallationTaskBase[] AdditionalInstallationTasks => new [] { new EnableSslAndKpiOnCluster() };
+
+		protected override string[] AdditionalServerSettings => new []
+		{
+			$"xpack.ssl.key={this.Node.FileSystem.NodePrivateKey}",
+			$"xpack.ssl.certificate={this.Node.FileSystem.NodeCertificate}",
+			$"xpack.ssl.certificate_authorities={this.Node.FileSystem.CaCertificate}",
+			"xpack.security.transport.ssl.enabled=true",
+			"xpack.security.http.ssl.enabled=true",
+		};
+
+		private static int _port = 9200;
+		private int? _desiredPort;
+		public override int DesiredPort
+		{
+			get
+			{
+				if (!this._desiredPort.HasValue)
+					this._desiredPort = ++_port;
+				return this._desiredPort.Value;
+			}
+		}
+
+		public override ConnectionSettings ClusterConnectionSettings(ConnectionSettings s) =>
+			this.ConnectionSettings(Authenticate(s));
+
+		public virtual ConnectionSettings Authenticate(ConnectionSettings s) =>
+			s.BasicAuthentication("es_admin", "es_admin");
+
+		protected abstract ConnectionSettings ConnectionSettings(ConnectionSettings s);
+	}
+
+	public class EnableSslAndKpiOnCluster : InstallationTaskBase
+	{
+		public override void Run(NodeConfiguration config, NodeFileSystem fileSystem)
+		{
+			//due to a bug in certgen this file needs to live in two places
+			var silentModeConfigFile  = Path.Combine(fileSystem.ElasticsearchHome, "certgen") + ".yml";
+			var silentModeConfigFileDuplicate  = Path.Combine(fileSystem.ConfigPath, "x-pack", "certgen") + ".yml";
+			foreach(var file in new []{silentModeConfigFile, silentModeConfigFileDuplicate})
+                if (!File.Exists(file)) File.WriteAllLines(file, new []
+                {
+                    "instances:",
+                    $"    - name : \"{fileSystem.CertificateNodeName}\"",
+                    $"    - name : \"{fileSystem.ClientCertificateName}\"",
+                    $"      filename : \"{fileSystem.ClientCertificateFilename}\"",
+                });
+
+			this.GenerateCertificates(fileSystem, silentModeConfigFile);
+			this.GenerateUnusedCertificates(fileSystem, silentModeConfigFile);
+			this.AddClientCertificateUser(fileSystem);
+		}
+
+		private void AddClientCertificateUser(NodeFileSystem fileSystem)
+		{
+			var file = Path.Combine(fileSystem.ConfigPath, "x-pack", "role_mapping") + ".yml";
+			var name = fileSystem.ClientCertificateName;
+            if (!File.Exists(file) || !File.ReadAllLines(file).Any(f=>f.Contains(name))) File.WriteAllLines(file, new []
+            {
+             	"admin:",
+                $"    - \"{name}\""
+            });
+		}
+		private void GenerateCertificates(NodeFileSystem fileSystem, string silentModeConfigFile)
+		{
+			var name = fileSystem.CertificateFolderName;
+			if (!File.Exists(fileSystem.CaCertificate))
+				this.ExecuteBinary(fileSystem.CertGenBinary, "generating ssl certificates for this session",
+					"-in", silentModeConfigFile, "-out", $"{name}.zip");
+
+			if (Directory.Exists(fileSystem.CertificatesPath)) return;
+			Directory.CreateDirectory(fileSystem.CertificatesPath);
+			var zipLocation = Path.Combine(fileSystem.ConfigPath, "x-pack", name) + ".zip";
+			ZipFile.ExtractToDirectory(zipLocation, fileSystem.CertificatesPath);
+		}
+		private void GenerateUnusedCertificates(NodeFileSystem fileSystem, string silentModeConfigFile)
+		{
+			var name = fileSystem.UnusedCertificateFolderName;
+			if (!File.Exists(fileSystem.UnusedCaCertificate))
+				this.ExecuteBinary(fileSystem.CertGenBinary, "generating ssl certificates for this session",
+					"-in", silentModeConfigFile, "-out", $"{name}.zip");
+
+			if (Directory.Exists(fileSystem.UnusedCertificatesPath)) return;
+			Directory.CreateDirectory(fileSystem.UnusedCertificatesPath);
+			var zipLocation = Path.Combine(fileSystem.ConfigPath, "x-pack", name) + ".zip";
+			ZipFile.ExtractToDirectory(zipLocation, fileSystem.UnusedCertificatesPath);
+		}
+	}
+}
diff --git a/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs b/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
new file mode 100644
index 00000000000..ed4fca454ca
--- /dev/null
+++ b/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Elasticsearch.Net;
+using FluentAssertions;
+using Nest;
+using Tests.Framework;
+using Tests.Framework.Integration;
+
+namespace Tests.ClientConcepts.Certificates
+{
+	/**== Working with certificates
+     *
+	 * === Server Certificates
+	 *
+	 * If you've enabled SSL on elasticsearch with x-pack or through a proxy in front of elasticsearch and the Certificate Authority (CA)
+	 * That generated the certificate is trusted by the machine running the client code there should be nothing you'll have to do to to talk
+	 * to over https with the client. If you are using your own CA which is not trusted .NET won't allow you to make https calls to that endpoint.
+	 *
+     * .NET allows you to preempt this though through a custom validation through the the global static `ServicePointManager.ServerCertificateValidationCallback`.
+     * Most examples you will find on the .NET will simply return `true` from this delegate and call it quits. This is not advisable as this will allow any HTTPS
+     * traffic in the current AppDomain and not run any validations. Imagine you deploy a web app that talks to Elasticsearch over HTTPS but also some third party
+     * SOAP/WSDL endpoint setting `ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true;` will skip validation of BOTH
+     * Elasticsearch and that external web service.
+
+	 * .NET also allows you to set that callback per service endpoint and Elasticsearch.NET/NEST exposes this through connection settings.
+	 * You can do your own validation in that handler or simply assign baked in handler that we ship with out of the box on the static
+     * class `CertificateValidations`.
+	 *
+     * The two most basic ones are `AllowAll` and `DenyAll` which does accept or deny any ssl trafic to our nodes`:
+	 *
+	 */
+	public class DenyAllCertificatesCluster : SslAndKpiXPackCluster
+	{
+		protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) => s
+			.ServerCertificateValidationCallback((o, certificate, chain, errors) => false)
+			.ServerCertificateValidationCallback(CertificateValidations.DenyAll);
+	}
+	//hide
+	public class DenyAllSslCertificatesApiTests : ConnectionErrorTestBase
+	{
+		public DenyAllSslCertificatesApiTests(DenyAllCertificatesCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		[I] public async Task UsedHttps() => await AssertOnAllResponses(r => r.ApiCall.Uri.Scheme.Should().Be("https"));
+
+		protected override void AssertException(WebException e) =>
+			e.Message.Should().Contain("Could not establish trust relationship for the SSL/TLS secure channel.");
+
+		protected override void AssertException(HttpRequestException e) { }
+	}
+
+	public class AllowAllCertificatesCluster : SslAndKpiXPackCluster
+	{
+		protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) => s
+			.ServerCertificateValidationCallback((o, certificate, chain, errors) => true)
+			.ServerCertificateValidationCallback(CertificateValidations.AllowAll);
+	}
+	//hide
+	public class AllowAllSllCertificatesApiTests : CanConnectTestBase
+	{
+		public AllowAllSllCertificatesApiTests(AllowAllCertificatesCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		[I] public async Task UsedHttps() => await AssertOnAllResponses(r => r.ApiCall.Uri.Scheme.Should().Be("https"));
+	}
+
+	/**
+	 * If your client application however has access to the public CA certificate locally Elasticsearch.NET/NEST ships with handy helpers that assert
+	 * that the certificate that the server presented was one that came from our local CA certificate. If you use x-pack's `certgen` tool to
+	 * [generate SSL certificates](https://www.elastic.co/guide/en/x-pack/current/ssl-tls.html) the generated node certificate does not include the CA in the
+     * certificate chain. This to cut back on SSL handshake size. In those case you can use `CertificateValidations.AuthorityIsRoot` and pass it your local copy
+     * of the CA public key to assert that the certificate the server presented was generated off that.
+	 */
+
+	public class CertgenCaCluster : SslAndKpiXPackCluster
+	{
+		protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) => s
+			.ServerCertificateValidationCallback(
+				CertificateValidations.AuthorityIsRoot(new X509Certificate(this.Node.FileSystem.CaCertificate))
+			);
+	}
+
+	//hide
+	public class CertgenCaApiTests : CanConnectTestBase
+	{
+		public CertgenCaApiTests(CertgenCaCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		[I] public async Task UsedHttps() => await AssertOnAllResponses(r => r.ApiCall.Uri.Scheme.Should().Be("https"));
+	}
+
+	/**
+     * If your local copy does not match the servers CA Elasticsearch.NET/NEST will fail to connect
+	 */
+	public class BadCertgenCaCluster : SslAndKpiXPackCluster
+	{
+		protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) => s
+			.ServerCertificateValidationCallback(
+				CertificateValidations.AuthorityPartOfChain(new X509Certificate(this.Node.FileSystem.UnusedCaCertificate))
+			);
+	}
+
+	//hide
+	public class BadCertgenCaApiTests : ConnectionErrorTestBase
+	{
+		public BadCertgenCaApiTests(BadCertgenCaCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		[I] public async Task UsedHttps() => await AssertOnAllResponses(r => r.ApiCall.Uri.Scheme.Should().Be("https"));
+
+		protected override void AssertException(WebException e) =>
+			e.Message.Should().Contain("Could not establish trust relationship for the SSL/TLS secure channel.");
+
+		protected override void AssertException(HttpRequestException e) { }
+
+	}
+	/**
+     * If you go for a vendor generated SSL certificate its common practice for them to include the CA and any intermediary CA's in the certificate chain
+	 * in those case use `CertificateValidations.AuthorityPartOfChain` which validates that the local CA certificate is part of that chain and was used to
+     * generate the servers key.
+	 */
+
+#if !DOTNETCORE
+	/**
+	 * === Client Certificates
+     * X-Pack also allows you to configure a [PKI realm](https://www.elastic.co/guide/en/x-pack/current/pki-realm.html) to enable user authentication
+     * through client certificates. The `certgen` tool included with X-Pack allows you to
+     * [generate client certificates as well](https://www.elastic.co/guide/en/x-pack/current/ssl-tls.html#CO13-4) and assign the distinguished name (DN) of the
+     * certificate as a user with a certain role.
+     *
+     * certgen by default only generates a public certificate (`.cer`) and a private key `.key`. To authenticate with client certificates you need to present both
+     * as one certificate. The easiest way to do this is to generate a `pfx` or `p12` file from the two and present that to `new X509Certificate(pathToPfx)`.
+
+	 * If you do not have a way to run `openssl` or `Pvk2Pfx` to do so as part of your deployments the clients ships with a handy helper to generate one
+     * on the fly in code based of `.cer`  and `.key` files that `certgen` outputs. Sadly this is not available on .NET core because we can no longer set `PublicKey`
+	 * crypto service provider.
+
+	 * You can set Client Certificates to use on all connections on `ConnectionSettings`
+
+ 	 */
+	public class PkiCluster : CertgenCaCluster
+	{
+		public override ConnectionSettings Authenticate(ConnectionSettings s) => s
+			//.ClientCertificate(this.Node.FileSystem.ClientCertificate);
+			.ClientCertificate(
+				ClientCertificate.LoadWithPrivateKey(this.Node.FileSystem.ClientCertificate, this.Node.FileSystem.ClientPrivateKey, "")
+			);
+
+		protected override string[] AdditionalServerSettings => base.AdditionalServerSettings.Concat(new []
+		{
+			"xpack.security.authc.realms.file1.enabled=false",
+			"xpack.security.http.ssl.client_authentication=required"
+		}).ToArray();
+	}
+	//hide
+	public class PkiApiTests : CanConnectTestBase
+	{
+		public PkiApiTests(PkiCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		[I] public async Task UsedHttps() => await AssertOnAllResponses(r => r.ApiCall.Uri.Scheme.Should().Be("https"));
+	}
+
+	/**
+     * Or per request on `RequestConfiguration` which will take precedence over the ones defined on `ConnectionConfiguration`
+	 */
+	public class BadPkiCluster : PkiCluster {}
+	public class BadCustomCertificatePerRequestWinsApiTests : ConnectionErrorTestBase
+	{
+		public BadCustomCertificatePerRequestWinsApiTests(BadPkiCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		[I] public async Task UsedHttps() => await AssertOnAllResponses(r => r.ApiCall.Uri.Scheme.Should().Be("https"));
+
+		private string BadCertificate => this.Cluster.Node.FileSystem.ClientCertificate;
+
+		protected override RootNodeInfoRequest Initializer => new RootNodeInfoRequest
+		{
+			RequestConfiguration = new RequestConfiguration
+			{
+				ClientCertificates = new X509Certificate2Collection { new X509Certificate2(this.BadCertificate) }
+			}
+		};
+
+		protected override Func Fluent => s => s
+			.RequestConfiguration(r => r
+				.ClientCertificate(this.BadCertificate)
+
+			);
+
+		protected override void AssertException(WebException e) =>
+			e.Message.Should().Contain("Could not create SSL/TLS secure channel");
+
+		protected override void AssertException(HttpRequestException e) { }
+
+	}
+#endif
+
+}
diff --git a/src/Tests/Framework/EndpointTests/ApiIntegrationAgainstNewIndexTestBase.cs b/src/Tests/Framework/EndpointTests/ApiIntegrationAgainstNewIndexTestBase.cs
new file mode 100644
index 00000000000..a91c41dc06f
--- /dev/null
+++ b/src/Tests/Framework/EndpointTests/ApiIntegrationAgainstNewIndexTestBase.cs
@@ -0,0 +1,29 @@
+using System.Linq;
+using Elasticsearch.Net;
+using Nest;
+using Tests.Framework.Integration;
+using Tests.Framework.ManagedElasticsearch.Clusters;
+
+namespace Tests.Framework
+{
+	public abstract class ApiIntegrationAgainstNewIndexTestBase
+		: ApiIntegrationTestBase
+		where TCluster : ClusterBase, new()
+		where TResponse : class, IResponse
+		where TDescriptor : class, TInterface
+		where TInitializer : class, TInterface
+		where TInterface : class
+	{
+		protected ApiIntegrationAgainstNewIndexTestBase(ClusterBase cluster, EndpointUsage usage) : base(cluster, usage) { }
+
+		protected override void IntegrationSetup(IElasticClient client, CallUniqueValues values)
+		{
+			foreach (var index in values.Values) client.CreateIndex(index, this.CreateIndexSettings).ShouldBeValid();
+			var indices = Infer.Indices(values.Values.Select(i => (IndexName)i));
+			client.ClusterHealth(f => f.WaitForStatus(WaitForStatus.Yellow).Index(indices))
+				.ShouldBeValid();
+		}
+
+		protected virtual ICreateIndexRequest CreateIndexSettings(CreateIndexDescriptor create) => create;
+	}
+}
\ No newline at end of file
diff --git a/src/Tests/Framework/EndpointTests/ApiIntegrationTestBase.cs b/src/Tests/Framework/EndpointTests/ApiIntegrationTestBase.cs
index 7b6872fe628..a09b7554eea 100644
--- a/src/Tests/Framework/EndpointTests/ApiIntegrationTestBase.cs
+++ b/src/Tests/Framework/EndpointTests/ApiIntegrationTestBase.cs
@@ -1,9 +1,7 @@
 using System;
-using System.Linq;
 using System.Net;
 using System.Runtime.ExceptionServices;
 using System.Threading.Tasks;
-using Elasticsearch.Net;
 using FluentAssertions;
 using Nest;
 using Tests.Framework.Integration;
@@ -29,16 +27,13 @@ protected ApiIntegrationTestBase(ClusterBase cluster, EndpointUsage usage) : bas
 		protected override IElasticClient Client => this.Cluster.Client;
 		protected override TInitializer Initializer => Activator.CreateInstance();
 
-		[I]
-		protected async Task HandlesStatusCode() =>
+		[I] public async Task HandlesStatusCode() =>
 			await this.AssertOnAllResponses(r => r.ApiCall.HttpStatusCode.Should().Be(this.ExpectStatusCode));
 
-		[I]
-		protected async Task ReturnsExpectedIsValid() =>
+		[I] public async Task ReturnsExpectedIsValid() =>
 			await this.AssertOnAllResponses(r => r.ShouldHaveExpectedIsValid(this.ExpectIsValid));
 
-		[I]
-		protected async Task ReturnsExpectedResponse() => await this.AssertOnAllResponses(ExpectResponse);
+		[I] public async Task ReturnsExpectedResponse() => await this.AssertOnAllResponses(ExpectResponse);
 
 		protected override Task AssertOnAllResponses(Action assert)
 		{
@@ -66,25 +61,4 @@ private static bool IsNotRequestExceptionType(Type exceptionType)
 #endif
 		}
 	}
-
-	public abstract class ApiIntegrationAgainstNewIndexTestBase
-		: ApiIntegrationTestBase
-		where TCluster : ClusterBase, new()
-		where TResponse : class, IResponse
-		where TDescriptor : class, TInterface
-		where TInitializer : class, TInterface
-		where TInterface : class
-	{
-		protected ApiIntegrationAgainstNewIndexTestBase(ClusterBase cluster, EndpointUsage usage) : base(cluster, usage) { }
-
-		protected override void IntegrationSetup(IElasticClient client, CallUniqueValues values)
-		{
-			foreach (var index in values.Values) client.CreateIndex(index, this.CreateIndexSettings).ShouldBeValid();
-			var indices = Infer.Indices(values.Values.Select(i => (IndexName)i));
-			client.ClusterHealth(f => f.WaitForStatus(WaitForStatus.Yellow).Index(indices))
-				.ShouldBeValid();
-		}
-
-		protected virtual ICreateIndexRequest CreateIndexSettings(CreateIndexDescriptor create) => create;
-	}
 }
diff --git a/src/Tests/Framework/EndpointTests/CanConnectTestBase.cs b/src/Tests/Framework/EndpointTests/CanConnectTestBase.cs
new file mode 100644
index 00000000000..01127dd4926
--- /dev/null
+++ b/src/Tests/Framework/EndpointTests/CanConnectTestBase.cs
@@ -0,0 +1,34 @@
+using Elasticsearch.Net;
+using FluentAssertions;
+using Nest;
+using Tests.Framework.Integration;
+using Tests.Framework.ManagedElasticsearch.Clusters;
+
+namespace Tests.Framework
+{
+	public abstract class CanConnectTestBase :
+		ApiIntegrationTestBase
+		where TCluster : ClusterBase, new()
+	{
+		protected CanConnectTestBase(TCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
+		protected override LazyResponses ClientUsage() => Calls(
+			fluent: (client, f) => client.RootNodeInfo(),
+			fluentAsync: (client, f) => client.RootNodeInfoAsync(),
+			request: (client, r) => client.RootNodeInfo(r),
+			requestAsync: (client, r) => client.RootNodeInfoAsync(r)
+		);
+
+		protected override bool ExpectIsValid => true;
+		protected override int ExpectStatusCode => 200;
+		protected override HttpMethod HttpMethod => HttpMethod.GET;
+		protected override string UrlPath => "/";
+
+		protected override void ExpectResponse(IRootNodeInfoResponse response)
+		{
+			response.Version.Should().NotBeNull();
+			response.Version.LuceneVersion.Should().NotBeNullOrWhiteSpace();
+			response.Tagline.Should().NotBeNullOrWhiteSpace();
+			response.Name.Should().NotBeNullOrWhiteSpace();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/Tests/Framework/EndpointTests/ConnectionErrorTestBase.cs b/src/Tests/Framework/EndpointTests/ConnectionErrorTestBase.cs
new file mode 100644
index 00000000000..c5aae575e57
--- /dev/null
+++ b/src/Tests/Framework/EndpointTests/ConnectionErrorTestBase.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Elasticsearch.Net;
+using FluentAssertions;
+using Nest;
+using Tests.Framework.Integration;
+using Tests.Framework.ManagedElasticsearch.Clusters;
+
+namespace Tests.Framework
+{
+	public abstract class ConnectionErrorTestBase
+		: ApiTestBase
+		where TCluster : ClusterBase, new()
+	{
+		protected ConnectionErrorTestBase(ClusterBase cluster, EndpointUsage usage) : base(cluster, usage) { }
+
+		protected override LazyResponses ClientUsage() => Calls(
+			fluent: (client, f) => client.RootNodeInfo(f),
+			fluentAsync: (client, f) => client.RootNodeInfoAsync(f),
+			request: (client, r) => client.RootNodeInfo(r),
+			requestAsync: (client, r) => client.RootNodeInfoAsync(r)
+		);
+
+		protected override IElasticClient Client => this.Cluster.Client;
+		protected override RootNodeInfoRequest Initializer => new RootNodeInfoRequest();
+
+		protected override string UrlPath => "";
+		protected override HttpMethod HttpMethod => HttpMethod.GET;
+
+		[I] public async Task IsValidIsFalse() => await this.AssertOnAllResponses(r => r.ShouldHaveExpectedIsValid(false));
+
+		[I] public async Task AssertException() => await this.AssertOnAllResponses(r =>
+		{
+			var e = r.OriginalException;
+			e.Should().NotBeNull();
+			if (e is WebException) this.AssertException((WebException) e);
+			else if (e is System.Net.Http.HttpRequestException)
+				this.AssertException((System.Net.Http.HttpRequestException) e);
+			else throw new Exception("Response orginal exception is not one of the expected connection exception but" + e.GetType().FullName);
+		});
+
+		protected abstract void AssertException(WebException e);
+		protected abstract void AssertException(System.Net.Http.HttpRequestException e);
+
+	}
+}
\ No newline at end of file
diff --git a/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs b/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs
index f6018c41c05..f3391c6a5e5 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs
@@ -25,15 +25,26 @@ protected ClusterBase()
 		public virtual int MaxConcurrency => 0;
 		protected virtual string[] AdditionalServerSettings { get; } = { };
 		protected virtual InstallationTaskBase[] AdditionalInstallationTasks { get; } = { };
+
+		public virtual bool EnableSsl { get; }
+		public virtual bool SkipValidation { get; }
+
+		public virtual int DesiredPort { get; } = 9200;
+
+		public virtual ConnectionSettings ClusterConnectionSettings(ConnectionSettings s) => s;
+
 		protected virtual void SeedNode() { }
 
 		public void Start()
 		{
-			this.TaskRunner.Install();
+			this.TaskRunner.Install(this.AdditionalInstallationTasks);
 			var nodeSettings = this.NodeConfiguration.CreateSettings(this.AdditionalServerSettings);
 			this.TaskRunner.OnBeforeStart(nodeSettings);
 			this.Node.Start(nodeSettings);
-			this.TaskRunner.ValidateAfterStart(this.Node.Client);
+			if (!this.SkipValidation)
+				this.TaskRunner.ValidateAfterStart(this.Node.Client);
+			if (this.Node.Port != this.DesiredPort)
+				throw new Exception($"The cluster that was started runs on {this.Node.Port} but this cluster wants {this.DesiredPort}");
 			this.SeedNode();
 		}
 
diff --git a/src/Tests/Framework/ManagedElasticsearch/Clusters/XPackCluster.cs b/src/Tests/Framework/ManagedElasticsearch/Clusters/XPackCluster.cs
index a8c46def75e..a32b6249660 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Clusters/XPackCluster.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Clusters/XPackCluster.cs
@@ -1,8 +1,13 @@
-using Tests.Framework.Integration;
+using Nest;
+using Tests.Framework.Integration;
 using Tests.Framework.ManagedElasticsearch.Plugins;
 
 namespace Tests.Framework.ManagedElasticsearch.Clusters
 {
 	[RequiresPlugin(ElasticsearchPlugin.XPack)]
-	public class XPackCluster : ClusterBase { }
+	public class XPackCluster : ClusterBase
+	{
+		public override ConnectionSettings ClusterConnectionSettings(ConnectionSettings s) =>
+			s.BasicAuthentication("es_admin", "es_admin");
+	}
 }
diff --git a/src/Tests/Framework/ManagedElasticsearch/Nodes/ElasticsearchNode.cs b/src/Tests/Framework/ManagedElasticsearch/Nodes/ElasticsearchNode.cs
index ab3c0507507..7ac0ea1a044 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Nodes/ElasticsearchNode.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Nodes/ElasticsearchNode.cs
@@ -57,7 +57,7 @@ public IElasticClient Client
 					if (this._client != null) return this._client;
 
 					var port = this.Started ? this.Port : 9200;
-					this._client = TestClient.GetClient(ComposeSettings, port);
+					this._client = TestClient.GetClient(ComposeSettings, port, forceSsl: this._config.EnableSsl);
 					return this.Client;
 				}
 			}
@@ -133,21 +133,22 @@ private void HandleConsoleMessage(ElasticsearchConsoleOut consoleOut, XplatManua
 		}
 
 		private ConnectionSettings ClusterSettings(ConnectionSettings s, Func settings) =>
-			AddBasicAuthentication(AppendClusterNameToHttpHeaders(settings(s)));
+			AddClusterSpecificConnectionSettings(AppendClusterNameToHttpHeaders(settings(s)));
 
 		private IElasticClient GetPrivateClient(Func settings, bool forceInMemory, int port)
 		{
 			settings = settings ?? (s => s);
 			var client = forceInMemory
 				? TestClient.GetInMemoryClient(s => ClusterSettings(s, settings), port)
-				: TestClient.GetClient(s => ClusterSettings(s, settings), port);
+				: TestClient.GetClient(s => ClusterSettings(s, settings), port, forceSsl: this._config.EnableSsl);
 			return client;
 		}
 
-		private ConnectionSettings ComposeSettings(ConnectionSettings s) => AddBasicAuthentication(AppendClusterNameToHttpHeaders(s));
+		private ConnectionSettings ComposeSettings(ConnectionSettings s) =>
+			AddClusterSpecificConnectionSettings(AppendClusterNameToHttpHeaders(s));
 
-		private ConnectionSettings AddBasicAuthentication(ConnectionSettings settings) =>
-			!this._config.XPackEnabled ? settings : settings.BasicAuthentication("es_admin", "es_admin");
+		private ConnectionSettings AddClusterSpecificConnectionSettings(ConnectionSettings settings) =>
+			this._config.ClusterConnectionSettings(settings);
 
 		private ConnectionSettings AppendClusterNameToHttpHeaders(ConnectionSettings settings)
 		{
diff --git a/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeConfiguration.cs b/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeConfiguration.cs
index 04bf58202ef..5524278ee04 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeConfiguration.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeConfiguration.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Nest;
 using Tests.Framework.Configuration;
 using Tests.Framework.ManagedElasticsearch.Clusters;
 using Tests.Framework.ManagedElasticsearch.Plugins;
@@ -20,10 +21,13 @@ public class NodeConfiguration : ITestConfiguration
 		public string ClusterFilter { get; }
 		public string TestFilter { get; }
 		public NodeFileSystem FileSystem { get; }
+		public int DesiredPort { get; }
 
 		public ElasticsearchPlugin[] RequiredPlugins { get; } = { };
 
 		public bool XPackEnabled => this.RequiredPlugins.Contains(ElasticsearchPlugin.XPack);
+		public bool EnableSsl { get; }
+		public ConnectionSettings ClusterConnectionSettings(ConnectionSettings s) => _cluster.ClusterConnectionSettings(s);
 
 		private readonly string _uniqueSuffix = Guid.NewGuid().ToString("N").Substring(0, 6);
 		public string ClusterMoniker => this._cluster.GetType().Name.Replace("Cluster", "").ToLowerInvariant();
@@ -35,6 +39,7 @@ public class NodeConfiguration : ITestConfiguration
 		public NodeConfiguration(ITestConfiguration configuration, ClusterBase cluster)
 		{
 			this._cluster = cluster;
+			this.EnableSsl = cluster.SkipValidation;
 
 			this.RequiredPlugins = ClusterRequiredPlugins(cluster);
 			this.Mode = configuration.Mode;
@@ -48,23 +53,28 @@ public NodeConfiguration(ITestConfiguration configuration, ClusterBase cluster)
 			this.ClusterFilter = configuration.ClusterFilter;
 			this.TestFilter = configuration.TestFilter;
 			this.FileSystem = new NodeFileSystem(configuration.ElasticsearchVersion, this.ClusterName, this.NodeName);
+			this.DesiredPort = cluster.DesiredPort;
 
 			var attr = v.Major >= 5 ? "attr." : "";
 			var indexedOrStored = v > new ElasticsearchVersion("5.0.0-alpha1") ? "stored" : "indexed";
 			var shieldOrSecurity = v > new ElasticsearchVersion("5.0.0-alpha1") ? "xpack.security" : "shield";
 			var es = v > new ElasticsearchVersion("5.0.0-alpha2") ? "" : "es.";
 			var b = this.XPackEnabled.ToString().ToLowerInvariant();
+			var sslEnabled = this.EnableSsl.ToString().ToLowerInvariant();
 			this.DefaultNodeSettings = new List
 			{
 				$"{es}cluster.name={this.ClusterName}",
 				$"{es}node.name={this.NodeName}",
 				$"{es}path.repo={this.FileSystem.RepositoryPath}",
 				$"{es}path.data={this.FileSystem.DataPath}",
+				$"{es}http.port={this.DesiredPort}",
 				$"{es}script.inline=true",
 				$"{es}script.max_compilations_per_minute=10000",
 				$"{es}script.{indexedOrStored}=true",
 				$"{es}node.{attr}testingcluster=true",
-				$"{es}{shieldOrSecurity}.enabled={b}"
+				$"{es}{shieldOrSecurity}.enabled={b}",
+				$"{es}{shieldOrSecurity}.http.ssl.enabled={sslEnabled}",
+				$"{es}{shieldOrSecurity}.authc.realms.pki1.enabled={sslEnabled}",
 			};
 		}
 
diff --git a/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeFileSystem.cs b/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeFileSystem.cs
index 1b8404efcff..86e2fc5c8d3 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeFileSystem.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Nodes/NodeFileSystem.cs
@@ -26,6 +26,26 @@ public class NodeFileSystem
 		public string DownloadZipLocation => Path.Combine(this.RoamingFolder, this._version.Zip);
 		public string TaskRunnerFile => Path.Combine(this.RoamingFolder, "taskrunner.log");
 
+
+		//certificates
+		public string CertGenBinary => Path.Combine(this.ElasticsearchHome, "bin", "x-pack", "certgen") + ".bat";
+
+		public string CertificateFolderName => "node-certificates";
+		public string CertificateNodeName => "node01";
+		public string ClientCertificateName => "cn=John Doe,ou=example,o=com";
+		public string ClientCertificateFilename => "john_doe";
+		public string CertificatesPath => Path.Combine(this.ConfigPath, this.CertificateFolderName);
+		public string CaCertificate => Path.Combine(this.CertificatesPath, "ca", "ca") + ".crt";
+		public string NodePrivateKey => Path.Combine(this.CertificatesPath, this.CertificateNodeName, this.CertificateNodeName) + ".key";
+		public string NodeCertificate => Path.Combine(this.CertificatesPath, this.CertificateNodeName, this.CertificateNodeName) + ".crt";
+		public string ClientCertificate => Path.Combine(this.CertificatesPath, this.ClientCertificateFilename, this.ClientCertificateFilename) + ".crt";
+		public string ClientPrivateKey => Path.Combine(this.CertificatesPath, this.ClientCertificateFilename, this.ClientCertificateFilename) + ".key";
+
+		public string UnusedCertificateFolderName => $"unused-{CertificateFolderName}";
+		public string UnusedCertificatesPath => Path.Combine(this.ConfigPath, this.UnusedCertificateFolderName);
+		public string UnusedCaCertificate => Path.Combine(this.UnusedCertificatesPath, "ca", "ca") + ".crt";
+		public string UnusedClientCertificate => Path.Combine(this.UnusedCertificatesPath, this.ClientCertificateFilename, this.ClientCertificateFilename) + ".crt";
+
 		public NodeFileSystem(ElasticsearchVersion version, string clusterName, string nodeName)
 		{
 			this._version = version;
diff --git a/src/Tests/Framework/ManagedElasticsearch/Tasks/InstallationTasks/EnsureSecurityRealms.cs b/src/Tests/Framework/ManagedElasticsearch/Tasks/InstallationTasks/EnsureSecurityRealms.cs
new file mode 100644
index 00000000000..7358ff5a2b2
--- /dev/null
+++ b/src/Tests/Framework/ManagedElasticsearch/Tasks/InstallationTasks/EnsureSecurityRealms.cs
@@ -0,0 +1,56 @@
+using System.IO;
+using System.Linq;
+using Tests.Framework.Configuration;
+using Tests.Framework.Integration;
+using Tests.Framework.ManagedElasticsearch.Nodes;
+
+namespace Tests.Framework.ManagedElasticsearch.Tasks.InstallationTasks
+{
+	public class EnsureSecurityRealms : InstallationTaskBase
+	{
+		public override void Run(NodeConfiguration config, NodeFileSystem fileSystem)
+		{
+			var configFile = Path.Combine(fileSystem.ElasticsearchHome, "config", "elasticsearch.yml");
+			var lines = File.ReadAllLines(configFile).ToList();
+			var saveFile = false;
+
+			// set up for Watcher HipChat action
+			if (!lines.Any(line => line.Contains("file1")))
+			{
+				lines.AddRange(new[]
+				{
+					string.Empty,
+					"xpack:",
+					"  security:",
+					"    authc:",
+					"      realms:",
+					"        file1:",
+					"          type: file",
+					"          order: 0",
+					string.Empty
+				});
+				saveFile = true;
+			}
+
+			// set up for Watcher HipChat action
+			if (!lines.Any(line => line.Contains("pki1")))
+			{
+				lines.AddRange(new[]
+				{
+					string.Empty,
+					"xpack:",
+					"  security:",
+					"    authc:",
+					"      realms:",
+					"        pki1:",
+					"          type: pki",
+					"          order: 1",
+					string.Empty
+				});
+				saveFile = true;
+			}
+
+			if (saveFile) File.WriteAllLines(configFile, lines);
+		}
+	}
+}
diff --git a/src/Tests/Framework/ManagedElasticsearch/Tasks/NodeTaskRunner.cs b/src/Tests/Framework/ManagedElasticsearch/Tasks/NodeTaskRunner.cs
index 4399ab7b6ba..d5c3a4d7d87 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Tasks/NodeTaskRunner.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Tasks/NodeTaskRunner.cs
@@ -35,6 +35,7 @@ public NodeTaskRunner(NodeConfiguration nodeConfiguration)
 			new EnsureSecurityRolesFileExists(),
 			new EnsureWatcherActionConfigurationInElasticsearchYaml(),
 			new EnsureSecurityRolesFileExists(),
+			new EnsureSecurityRealms(),
 			new EnsureSecurityUsersInDefaultRealmAreAdded(),
 		};
 		private static IEnumerable BeforeStart { get; } = new List
@@ -54,8 +55,11 @@ public NodeTaskRunner(NodeConfiguration nodeConfiguration)
 			new ValidateClusterStateTask()
 		};
 
-		public void Install()=>
-			Itterate(InstallationTasks, (t, n,  fs) => t.Run(n, fs));
+		public void Install(InstallationTaskBase[] additionalInstallationTasks)=>
+			Itterate(
+				InstallationTasks.Concat(additionalInstallationTasks ?? Enumerable.Empty()),
+				(t, n,  fs) => t.Run(n, fs)
+			);
 
 		public void Dispose() =>
 			Itterate(NodeStoppedTasks, (t, n,  fs) => t.Run(n, fs));
diff --git a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateClusterStateTask.cs b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateClusterStateTask.cs
index 17764a5221e..80446e50ebe 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateClusterStateTask.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateClusterStateTask.cs
@@ -21,4 +21,4 @@ public override void Validate(IElasticClient client, NodeConfiguration configura
 				throw new Exception($"Did not see a healhty cluster before calling onNodeStarted handler." + healthyCluster.DebugInformation);
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateLicenseTask.cs b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateLicenseTask.cs
index 581d1ca3d93..ea9878abf94 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateLicenseTask.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateLicenseTask.cs
@@ -1,4 +1,5 @@
 using System;
+using Elasticsearch.Net;
 using Nest;
 using Tests.Framework.Configuration;
 using Tests.Framework.Integration;
@@ -11,6 +12,7 @@ public class ValidateLicenseTask : NodeValidationTaskBase
 		public override void Validate(IElasticClient client, NodeConfiguration configuration)
 		{
 			if (!configuration.XPackEnabled) return;
+
 			var license = client.GetLicense();
 			if (license.IsValid && license.License.Status == LicenseStatus.Active) return;
 
@@ -22,7 +24,10 @@ public override void Validate(IElasticClient client, NodeConfiguration configura
 #endif
 			if (!string.IsNullOrWhiteSpace(licenseFile))
 			{
-				var putLicense = client.PostLicense(new PostLicenseRequest {License = License.LoadFromDisk(licenseFile)});
+				var putLicense = client.PostLicense(new PostLicenseRequest
+				{
+					License = License.LoadFromDisk(licenseFile)
+				});
 				if (!putLicense.IsValid)
 					throw new Exception("Server has invalid license and the ES_LICENSE_FILE failed to register\r\n" + putLicense.DebugInformation);
 
@@ -51,4 +56,4 @@ public override void Validate(IElasticClient client, NodeConfiguration configura
 				throw exception;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidatePluginsTask.cs b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidatePluginsTask.cs
index 9db28bceb42..8ac84a8402b 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidatePluginsTask.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidatePluginsTask.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using Elasticsearch.Net;
 using Nest;
 using Tests.Framework.Configuration;
 using Tests.Framework.Integration;
@@ -37,4 +38,4 @@ public override void Validate(IElasticClient client, NodeConfiguration configura
 			throw new Exception($"Already running elasticsearch missed the following plugin(s): {pluginsString}.");
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateRunningVersion.cs b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateRunningVersion.cs
index 359f90d02e2..dea19e38ba7 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateRunningVersion.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Tasks/ValidationTasks/ValidateRunningVersion.cs
@@ -1,4 +1,5 @@
 using System;
+using Elasticsearch.Net;
 using Nest;
 using Tests.Framework.Configuration;
 using Tests.Framework.Integration;
diff --git a/src/Tests/Framework/TestClient.cs b/src/Tests/Framework/TestClient.cs
index 3d64b5c7cdb..b85bf4fda3f 100644
--- a/src/Tests/Framework/TestClient.cs
+++ b/src/Tests/Framework/TestClient.cs
@@ -24,7 +24,8 @@ public static class TestClient
 		public static IElasticClient Default = new ElasticClient(GlobalDefaultSettings);
 		public static IElasticClient DefaultInMemoryClient = GetInMemoryClient();
 
-		public static Uri CreateUri(int port = 9200) => new UriBuilder("http", Host, port).Uri;
+		public static Uri CreateUri(int port = 9200, bool forceSsl = false) =>
+			new UriBuilder(forceSsl ? "https" : "http", Host, port).Uri;
 
 		public static string Host => (RunningFiddler) ? "ipv4.fiddler" : "localhost";
 
@@ -110,13 +111,14 @@ public static ConnectionSettings CreateSettings(
 			Func modifySettings = null,
 			int port = 9200,
 			bool forceInMemory = false,
+			bool forceSsl = false,
 			Func createPool = null,
 			Func serializerFactory = null
 		)
 		{
 			createPool = createPool ?? (u => new SingleNodeConnectionPool(u));
 #pragma warning disable CS0618 // Type or member is obsolete
-			var defaultSettings = DefaultSettings(new ConnectionSettings(createPool(CreateUri(port)),
+			var defaultSettings = DefaultSettings(new ConnectionSettings(createPool(CreateUri(port, forceSsl)),
 				CreateConnection(forceInMemory: forceInMemory), serializerFactory));
 #pragma warning restore CS0618 // Type or member is obsolete
 			var settings = modifySettings != null ? modifySettings(defaultSettings) : defaultSettings;
@@ -131,8 +133,16 @@ public static IElasticClient GetInMemoryClientWithSerializerFactory(
 			Func serializerFactory) =>
 			new ElasticClient(CreateSettings(modifySettings, forceInMemory: true, serializerFactory: serializerFactory));
 
-		public static IElasticClient GetClient(Func modifySettings = null,
-			int port = 9200, Func createPool = null) =>
+		public static IElasticClient GetClient(
+			Func modifySettings = null,
+			int port = 9200,
+			bool forceSsl = false) =>
+			new ElasticClient(CreateSettings(modifySettings, port, forceInMemory: false, forceSsl: forceSsl));
+
+		public static IElasticClient GetClient(
+			Func createPool,
+			Func modifySettings = null,
+			int port = 9200) =>
 			new ElasticClient(CreateSettings(modifySettings, port, forceInMemory: false, createPool: createPool));
 
 		public static IConnection CreateConnection(ConnectionSettings settings = null, bool forceInMemory = false) =>
diff --git a/src/Tests/Framework/Xunit/TestAssemblyRunner.cs b/src/Tests/Framework/Xunit/TestAssemblyRunner.cs
index 55151ffe3d0..1a7cd0e624b 100644
--- a/src/Tests/Framework/Xunit/TestAssemblyRunner.cs
+++ b/src/Tests/Framework/Xunit/TestAssemblyRunner.cs
@@ -128,6 +128,7 @@ private IEnumerable ParseExcludedClusters(string clusterFilter)
 				typeof(ClusterBase).Assembly()
 #else
 				typeof(ClusterBase).Assembly
+#endif
 #endif
 				.GetTypes()
 				.Where(t => typeof(ClusterBase).IsAssignableFrom(t) && t != typeof(ClusterBase))
diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj
index 9c988a4c44c..805cdb896f1 100644
--- a/src/Tests/Tests.csproj
+++ b/src/Tests/Tests.csproj
@@ -231,6 +231,8 @@
     
     
     
+    
+    
     
     
     
@@ -261,8 +263,11 @@
     
     
     
+    
     
     
+    
+    
     
     
     
@@ -290,6 +295,7 @@
     
     
     
+    
     
     
     
@@ -842,6 +848,7 @@
     
   
   
+    
     
     
     

From 0bb2694cda9ef9118ef3645a2b2515de6c779972 Mon Sep 17 00:00:00 2001
From: Mpdreamz 
Date: Thu, 2 Mar 2017 13:13:18 +0100
Subject: [PATCH 2/6] spacing and visibillity changes

---
 .../Connection/ClientCertificate.cs           |  5 +-
 .../WorkingWithCertificates.doc.cs            | 46 +++++++++----------
 2 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/src/Elasticsearch.Net/Connection/ClientCertificate.cs b/src/Elasticsearch.Net/Connection/ClientCertificate.cs
index 76a60cb43d2..18085ff3139 100644
--- a/src/Elasticsearch.Net/Connection/ClientCertificate.cs
+++ b/src/Elasticsearch.Net/Connection/ClientCertificate.cs
@@ -36,15 +36,14 @@ THE SOFTWARE.
 
 namespace Elasticsearch.Net
 {
-//.NET core does not allow to set PrivateKey, you'll have to manually convert to pfx/p12 or add the key to the machine store
+//.NET removed the setter for PrivateKey for X509Certificate, you'll have to manually convert to pfx/p12 or add the key to the machine store
 #if !DOTNETCORE
 
 	public class ClientCertificate
 	{
 		//https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem
-		public static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] privkey)
+		private static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] privkey)
 		{
-			// ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
 			using (var mem = new MemoryStream(privkey))
 			using (var binr = new BinaryReader(mem))
 			{
diff --git a/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs b/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
index ed4fca454ca..d5c14707934 100644
--- a/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
+++ b/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
@@ -13,24 +13,24 @@
 namespace Tests.ClientConcepts.Certificates
 {
 	/**== Working with certificates
-     *
+	 *
 	 * === Server Certificates
 	 *
 	 * If you've enabled SSL on elasticsearch with x-pack or through a proxy in front of elasticsearch and the Certificate Authority (CA)
 	 * That generated the certificate is trusted by the machine running the client code there should be nothing you'll have to do to to talk
 	 * to over https with the client. If you are using your own CA which is not trusted .NET won't allow you to make https calls to that endpoint.
 	 *
-     * .NET allows you to preempt this though through a custom validation through the the global static `ServicePointManager.ServerCertificateValidationCallback`.
-     * Most examples you will find on the .NET will simply return `true` from this delegate and call it quits. This is not advisable as this will allow any HTTPS
-     * traffic in the current AppDomain and not run any validations. Imagine you deploy a web app that talks to Elasticsearch over HTTPS but also some third party
-     * SOAP/WSDL endpoint setting `ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true;` will skip validation of BOTH
-     * Elasticsearch and that external web service.
+	 * .NET allows you to preempt this though through a custom validation through the the global static `ServicePointManager.ServerCertificateValidationCallback`.
+	 * Most examples you will find on the .NET will simply return `true` from this delegate and call it quits. This is not advisable as this will allow any HTTPS
+	 * traffic in the current AppDomain and not run any validations. Imagine you deploy a web app that talks to Elasticsearch over HTTPS but also some third party
+	 * SOAP/WSDL endpoint setting `ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true;` will skip validation of BOTH
+	 * Elasticsearch and that external web service.
 
 	 * .NET also allows you to set that callback per service endpoint and Elasticsearch.NET/NEST exposes this through connection settings.
 	 * You can do your own validation in that handler or simply assign baked in handler that we ship with out of the box on the static
-     * class `CertificateValidations`.
+	 * class `CertificateValidations`.
 	 *
-     * The two most basic ones are `AllowAll` and `DenyAll` which does accept or deny any ssl trafic to our nodes`:
+	 * The two most basic ones are `AllowAll` and `DenyAll` which does accept or deny any ssl trafic to our nodes`:
 	 *
 	 */
 	public class DenyAllCertificatesCluster : SslAndKpiXPackCluster
@@ -68,8 +68,8 @@ public AllowAllSllCertificatesApiTests(AllowAllCertificatesCluster cluster, Endp
 	 * If your client application however has access to the public CA certificate locally Elasticsearch.NET/NEST ships with handy helpers that assert
 	 * that the certificate that the server presented was one that came from our local CA certificate. If you use x-pack's `certgen` tool to
 	 * [generate SSL certificates](https://www.elastic.co/guide/en/x-pack/current/ssl-tls.html) the generated node certificate does not include the CA in the
-     * certificate chain. This to cut back on SSL handshake size. In those case you can use `CertificateValidations.AuthorityIsRoot` and pass it your local copy
-     * of the CA public key to assert that the certificate the server presented was generated off that.
+	 * certificate chain. This to cut back on SSL handshake size. In those case you can use `CertificateValidations.AuthorityIsRoot` and pass it your local copy
+	 * of the CA public key to assert that the certificate the server presented was generated off that.
 	 */
 
 	public class CertgenCaCluster : SslAndKpiXPackCluster
@@ -88,7 +88,7 @@ public CertgenCaApiTests(CertgenCaCluster cluster, EndpointUsage usage) : base(c
 	}
 
 	/**
-     * If your local copy does not match the servers CA Elasticsearch.NET/NEST will fail to connect
+	 * If your local copy does not match the servers CA Elasticsearch.NET/NEST will fail to connect
 	 */
 	public class BadCertgenCaCluster : SslAndKpiXPackCluster
 	{
@@ -111,29 +111,29 @@ protected override void AssertException(HttpRequestException e) { }
 
 	}
 	/**
-     * If you go for a vendor generated SSL certificate its common practice for them to include the CA and any intermediary CA's in the certificate chain
+	 * If you go for a vendor generated SSL certificate its common practice for them to include the CA and any intermediary CA's in the certificate chain
 	 * in those case use `CertificateValidations.AuthorityPartOfChain` which validates that the local CA certificate is part of that chain and was used to
-     * generate the servers key.
+	 * generate the servers key.
 	 */
 
 #if !DOTNETCORE
 	/**
 	 * === Client Certificates
-     * X-Pack also allows you to configure a [PKI realm](https://www.elastic.co/guide/en/x-pack/current/pki-realm.html) to enable user authentication
-     * through client certificates. The `certgen` tool included with X-Pack allows you to
-     * [generate client certificates as well](https://www.elastic.co/guide/en/x-pack/current/ssl-tls.html#CO13-4) and assign the distinguished name (DN) of the
-     * certificate as a user with a certain role.
-     *
-     * certgen by default only generates a public certificate (`.cer`) and a private key `.key`. To authenticate with client certificates you need to present both
-     * as one certificate. The easiest way to do this is to generate a `pfx` or `p12` file from the two and present that to `new X509Certificate(pathToPfx)`.
+	 * X-Pack also allows you to configure a [PKI realm](https://www.elastic.co/guide/en/x-pack/current/pki-realm.html) to enable user authentication
+	 * through client certificates. The `certgen` tool included with X-Pack allows you to
+	 * [generate client certificates as well](https://www.elastic.co/guide/en/x-pack/current/ssl-tls.html#CO13-4) and assign the distinguished name (DN) of the
+	 * certificate as a user with a certain role.
+	 *
+	 * certgen by default only generates a public certificate (`.cer`) and a private key `.key`. To authenticate with client certificates you need to present both
+	 * as one certificate. The easiest way to do this is to generate a `pfx` or `p12` file from the two and present that to `new X509Certificate(pathToPfx)`.
 
 	 * If you do not have a way to run `openssl` or `Pvk2Pfx` to do so as part of your deployments the clients ships with a handy helper to generate one
-     * on the fly in code based of `.cer`  and `.key` files that `certgen` outputs. Sadly this is not available on .NET core because we can no longer set `PublicKey`
+	 * on the fly in code based of `.cer`  and `.key` files that `certgen` outputs. Sadly this is not available on .NET core because we can no longer set `PublicKey`
 	 * crypto service provider.
 
 	 * You can set Client Certificates to use on all connections on `ConnectionSettings`
 
- 	 */
+	 */
 	public class PkiCluster : CertgenCaCluster
 	{
 		public override ConnectionSettings Authenticate(ConnectionSettings s) => s
@@ -156,7 +156,7 @@ public PkiApiTests(PkiCluster cluster, EndpointUsage usage) : base(cluster, usag
 	}
 
 	/**
-     * Or per request on `RequestConfiguration` which will take precedence over the ones defined on `ConnectionConfiguration`
+	 * Or per request on `RequestConfiguration` which will take precedence over the ones defined on `ConnectionConfiguration`
 	 */
 	public class BadPkiCluster : PkiCluster {}
 	public class BadCustomCertificatePerRequestWinsApiTests : ConnectionErrorTestBase

From 0ad9478451114cf4ae375e4d22f56c2d2984a6ae Mon Sep 17 00:00:00 2001
From: Mpdreamz 
Date: Tue, 14 Mar 2017 01:55:13 +0100
Subject: [PATCH 3/6] try fix mono build of .net 4.* HttpConnection

---
 src/Elasticsearch.Net/Connection/HttpConnection.cs | 12 +++++++-----
 src/Tests/Framework/Xunit/TestAssemblyRunner.cs    |  1 -
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs
index c8fecdaff75..bf9db5c9805 100644
--- a/src/Elasticsearch.Net/Connection/HttpConnection.cs
+++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs
@@ -14,14 +14,13 @@ namespace Elasticsearch.Net
 {
 	public class HttpConnection : IConnection
 	{
+		internal static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null;
+
 		static HttpConnection()
 		{
 			//Not available under mono
-			if (Type.GetType("Mono.Runtime") == null)
+			if (!IsMono)
 				HttpWebRequest.DefaultMaximumErrorResponseLength = -1;
-
-			ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
-				SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
 		}
 
 		protected virtual HttpWebRequest CreateHttpWebRequest(RequestData requestData)
@@ -41,13 +40,16 @@ protected virtual void SetClientCertificates(HttpWebRequest request, RequestData
 				request.ClientCertificates.AddRange(requestData.ClientCertificates);
 		}
 
-
 		protected virtual void SetServerCertificateValidationCallBackIfNeeded(HttpWebRequest request, RequestData requestData)
 		{
+			#if !__MonoCS__
 			//Only assign if one is defined on connection settings and a subclass has not already set one
 			var callback = requestData?.ConnectionSettings?.ServerCertificateValidationCallback;
 			if (callback != null && request.ServerCertificateValidationCallback == null)
 				request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(callback);
+			#else
+				throw new Exception("Mono misses ServerCertificateValidationCallback on HttpWebRequest");
+			#endif
 		}
 
 		protected virtual HttpWebRequest CreateWebRequest(RequestData requestData)
diff --git a/src/Tests/Framework/Xunit/TestAssemblyRunner.cs b/src/Tests/Framework/Xunit/TestAssemblyRunner.cs
index 1a7cd0e624b..55151ffe3d0 100644
--- a/src/Tests/Framework/Xunit/TestAssemblyRunner.cs
+++ b/src/Tests/Framework/Xunit/TestAssemblyRunner.cs
@@ -128,7 +128,6 @@ private IEnumerable ParseExcludedClusters(string clusterFilter)
 				typeof(ClusterBase).Assembly()
 #else
 				typeof(ClusterBase).Assembly
-#endif
 #endif
 				.GetTypes()
 				.Where(t => typeof(ClusterBase).IsAssignableFrom(t) && t != typeof(ClusterBase))

From ab41c03e68f6e6d39b24534f6100490e38f1ad2e Mon Sep 17 00:00:00 2001
From: Mpdreamz 
Date: Tue, 14 Mar 2017 02:48:37 +0100
Subject: [PATCH 4/6] make sure in unit test mode we skip the certificate tests
 since they rely on a disk on file, also make sure cluster base does not do
 the desiredport check when running in unit test mode

---
 .../Certificates/SslAndKpiXPackCluster.cs      |  2 +-
 .../WorkingWithCertificates.doc.cs             |  6 ++++++
 .../Clusters/ClusterBase.cs                    |  2 +-
 .../Framework/XUnitPlumbing/IntegrationOnly.cs |  6 ++++++
 .../XUnitPlumbing/RequiresPluginAttribute.cs   |  1 -
 .../XUnitPlumbing/UnitTestDiscoverer.cs        | 18 +++++++++++++++---
 src/Tests/Tests.csproj                         |  1 +
 7 files changed, 30 insertions(+), 6 deletions(-)
 create mode 100644 src/Tests/Framework/XUnitPlumbing/IntegrationOnly.cs

diff --git a/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs b/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs
index d16e3899842..a1da8c81eef 100644
--- a/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs
+++ b/src/Tests/ClientConcepts/Certificates/SslAndKpiXPackCluster.cs
@@ -10,7 +10,7 @@
 
 namespace Tests.ClientConcepts.Certificates
 {
-	[RequiresPlugin(ElasticsearchPlugin.XPack)]
+	[IntegrationOnly, RequiresPlugin(ElasticsearchPlugin.XPack)]
 	public abstract class SslAndKpiXPackCluster : XPackCluster
 	{
 		public override bool EnableSsl { get; } = true;
diff --git a/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs b/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
index d5c14707934..7f6f67612f7 100644
--- a/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
+++ b/src/Tests/ClientConcepts/Certificates/WorkingWithCertificates.doc.cs
@@ -40,6 +40,7 @@ protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) =
 			.ServerCertificateValidationCallback(CertificateValidations.DenyAll);
 	}
 	//hide
+	[IntegrationOnly]
 	public class DenyAllSslCertificatesApiTests : ConnectionErrorTestBase
 	{
 		public DenyAllSslCertificatesApiTests(DenyAllCertificatesCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
@@ -58,6 +59,7 @@ protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) =
 			.ServerCertificateValidationCallback(CertificateValidations.AllowAll);
 	}
 	//hide
+	[IntegrationOnly]
 	public class AllowAllSllCertificatesApiTests : CanConnectTestBase
 	{
 		public AllowAllSllCertificatesApiTests(AllowAllCertificatesCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
@@ -81,6 +83,7 @@ protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) =
 	}
 
 	//hide
+	[IntegrationOnly]
 	public class CertgenCaApiTests : CanConnectTestBase
 	{
 		public CertgenCaApiTests(CertgenCaCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
@@ -99,6 +102,7 @@ protected override ConnectionSettings ConnectionSettings(ConnectionSettings s) =
 	}
 
 	//hide
+	[IntegrationOnly]
 	public class BadCertgenCaApiTests : ConnectionErrorTestBase
 	{
 		public BadCertgenCaApiTests(BadCertgenCaCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
@@ -149,6 +153,7 @@ public override ConnectionSettings Authenticate(ConnectionSettings s) => s
 		}).ToArray();
 	}
 	//hide
+	[IntegrationOnly]
 	public class PkiApiTests : CanConnectTestBase
 	{
 		public PkiApiTests(PkiCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
@@ -159,6 +164,7 @@ public PkiApiTests(PkiCluster cluster, EndpointUsage usage) : base(cluster, usag
 	 * Or per request on `RequestConfiguration` which will take precedence over the ones defined on `ConnectionConfiguration`
 	 */
 	public class BadPkiCluster : PkiCluster {}
+	[IntegrationOnly]
 	public class BadCustomCertificatePerRequestWinsApiTests : ConnectionErrorTestBase
 	{
 		public BadCustomCertificatePerRequestWinsApiTests(BadPkiCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
diff --git a/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs b/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs
index f3391c6a5e5..3ff0baf4256 100644
--- a/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs
+++ b/src/Tests/Framework/ManagedElasticsearch/Clusters/ClusterBase.cs
@@ -43,7 +43,7 @@ public void Start()
 			this.Node.Start(nodeSettings);
 			if (!this.SkipValidation)
 				this.TaskRunner.ValidateAfterStart(this.Node.Client);
-			if (this.Node.Port != this.DesiredPort)
+			if (this.NodeConfiguration.RunIntegrationTests && this.Node.Port != this.DesiredPort)
 				throw new Exception($"The cluster that was started runs on {this.Node.Port} but this cluster wants {this.DesiredPort}");
 			this.SeedNode();
 		}
diff --git a/src/Tests/Framework/XUnitPlumbing/IntegrationOnly.cs b/src/Tests/Framework/XUnitPlumbing/IntegrationOnly.cs
new file mode 100644
index 00000000000..91233e12ebd
--- /dev/null
+++ b/src/Tests/Framework/XUnitPlumbing/IntegrationOnly.cs
@@ -0,0 +1,6 @@
+using System;
+
+namespace Tests.Framework
+{
+	public class IntegrationOnly : Attribute { }
+}
\ No newline at end of file
diff --git a/src/Tests/Framework/XUnitPlumbing/RequiresPluginAttribute.cs b/src/Tests/Framework/XUnitPlumbing/RequiresPluginAttribute.cs
index 5954b5fa6e2..ba105d3603a 100644
--- a/src/Tests/Framework/XUnitPlumbing/RequiresPluginAttribute.cs
+++ b/src/Tests/Framework/XUnitPlumbing/RequiresPluginAttribute.cs
@@ -19,5 +19,4 @@ public RequiresPluginAttribute(params ElasticsearchPlugin[] plugins)
 			this.Plugins = plugins.ToList();
 		}
 	}
-
 }
diff --git a/src/Tests/Framework/XUnitPlumbing/UnitTestDiscoverer.cs b/src/Tests/Framework/XUnitPlumbing/UnitTestDiscoverer.cs
index 54627a98ec8..5e0b770527a 100644
--- a/src/Tests/Framework/XUnitPlumbing/UnitTestDiscoverer.cs
+++ b/src/Tests/Framework/XUnitPlumbing/UnitTestDiscoverer.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Linq;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -7,9 +8,20 @@ namespace Tests.Framework
 	public class UnitTestDiscoverer : NestTestDiscoverer
 	{
 		public UnitTestDiscoverer(IMessageSink diagnosticMessageSink)
-			: base(diagnosticMessageSink, TestClient.Configuration.RunUnitTests) { }
+			: base(diagnosticMessageSink, TestClient.Configuration.RunUnitTests)
+		{
+		}
 
-		protected override bool SkipMethod(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) =>
-			!TestClient.Configuration.RunUnitTests;
+		protected override bool SkipMethod(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
+		{
+			var classOfMethod = Type.GetType(testMethod.TestClass.Class.Name, true, true);
+			return !TestClient.Configuration.RunUnitTests || ClassIsIntegrationOnly(classOfMethod);
+		}
+
+		private static bool ClassIsIntegrationOnly(Type classOfMethod)
+		{
+			var attributes = classOfMethod.GetAttributes();
+			return (attributes.Any());
+		}
 	}
 }
diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj
index 805cdb896f1..b6f4cb07415 100644
--- a/src/Tests/Tests.csproj
+++ b/src/Tests/Tests.csproj
@@ -360,6 +360,7 @@
     
     
     
+    
     
     
     

From 06af36f3c77ff6344515ebbc83466029a3faed47 Mon Sep 17 00:00:00 2001
From: Mpdreamz 
Date: Tue, 14 Mar 2017 02:59:16 +0100
Subject: [PATCH 5/6] only throw when attempting to set callback on mono when
 callback is not null

---
 src/Elasticsearch.Net/Connection/HttpConnection.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs
index bf9db5c9805..6cb6d0225bf 100644
--- a/src/Elasticsearch.Net/Connection/HttpConnection.cs
+++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs
@@ -48,7 +48,8 @@ protected virtual void SetServerCertificateValidationCallBackIfNeeded(HttpWebReq
 			if (callback != null && request.ServerCertificateValidationCallback == null)
 				request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(callback);
 			#else
-				throw new Exception("Mono misses ServerCertificateValidationCallback on HttpWebRequest");
+				if (callback != null)
+					throw new Exception("Mono misses ServerCertificateValidationCallback on HttpWebRequest");
 			#endif
 		}
 

From 90662484bdf8db46822290b60d55f6fc99c0f974 Mon Sep 17 00:00:00 2001
From: Mpdreamz 
Date: Tue, 14 Mar 2017 17:43:08 +0100
Subject: [PATCH 6/6] callback not in ifdef scope

---
 src/Elasticsearch.Net/Connection/HttpConnection.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs
index 6cb6d0225bf..1f0ca89c464 100644
--- a/src/Elasticsearch.Net/Connection/HttpConnection.cs
+++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs
@@ -42,9 +42,9 @@ protected virtual void SetClientCertificates(HttpWebRequest request, RequestData
 
 		protected virtual void SetServerCertificateValidationCallBackIfNeeded(HttpWebRequest request, RequestData requestData)
 		{
+			var callback = requestData?.ConnectionSettings?.ServerCertificateValidationCallback;
 			#if !__MonoCS__
 			//Only assign if one is defined on connection settings and a subclass has not already set one
-			var callback = requestData?.ConnectionSettings?.ServerCertificateValidationCallback;
 			if (callback != null && request.ServerCertificateValidationCallback == null)
 				request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(callback);
 			#else