diff --git a/src/Testcontainers/Builders/TlsEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/TlsEndpointAuthenticationProvider.cs
new file mode 100644
index 000000000..e8f5c9a80
--- /dev/null
+++ b/src/Testcontainers/Builders/TlsEndpointAuthenticationProvider.cs
@@ -0,0 +1,86 @@
+namespace DotNet.Testcontainers.Builders
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Net.Security;
+ using System.Security.Cryptography.X509Certificates;
+ using DotNet.Testcontainers.Configurations;
+ using DotNet.Testcontainers.Configurations.Credentials;
+
+ ///
+ internal sealed class TlsEndpointAuthenticationProvider : DockerEndpointAuthenticationProvider
+ {
+ private const string DefaultUserDockerFolderName = ".docker";
+ private const string DefaultCaCertFileName = "ca.pem";
+ private static readonly Uri DefaultTlsDockerEndpoint = new Uri("tcp://localhost:2376");
+ private static readonly string DefaultCertPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), DefaultUserDockerFolderName);
+
+ private readonly Lazy caCertificate;
+ private readonly Uri dockerEngine;
+ private readonly bool dockerTlsEnabled;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TlsEndpointAuthenticationProvider()
+ : this(PropertiesFileConfiguration.Instance, EnvironmentConfiguration.Instance)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A list of custom configurations.
+ public TlsEndpointAuthenticationProvider(params ICustomConfiguration[] customConfigurations)
+ {
+ var dockerCertPath = customConfigurations
+ .Select(customConfiguration => customConfiguration.GetDockerCertPath())
+ .FirstOrDefault(value => value != null) ?? DefaultCertPath;
+ var dockerCaCertFile = Path.Combine(dockerCertPath, DefaultCaCertFileName);
+
+ this.dockerEngine = customConfigurations
+ .Select(customConfiguration => customConfiguration.GetDockerHost())
+ .FirstOrDefault(value => value != null) ?? DefaultTlsDockerEndpoint;
+ this.dockerTlsEnabled = customConfigurations
+ .Select(customConfiguration => customConfiguration.GetDockerTls())
+ .Aggregate(false, (x, y) => x || y);
+ this.caCertificate = new Lazy(() => new X509Certificate2(dockerCaCertFile));
+ }
+
+ ///
+ public override bool IsApplicable()
+ {
+ return this.dockerTlsEnabled;
+ }
+
+ ///
+ public override IDockerEndpointAuthenticationConfiguration GetAuthConfig()
+ {
+ return new DockerEndpointAuthenticationConfiguration(this.dockerEngine, new TlsCredentials(this.ServerCertificateValidationCallback));
+ }
+
+ private bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ switch (sslPolicyErrors)
+ {
+ case SslPolicyErrors.None:
+ return true;
+ case SslPolicyErrors.RemoteCertificateNotAvailable:
+ case SslPolicyErrors.RemoteCertificateNameMismatch:
+ return false;
+ case SslPolicyErrors.RemoteCertificateChainErrors:
+ default:
+ var validationChain = new X509Chain();
+ validationChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ validationChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
+ validationChain.ChainPolicy.ExtraStore.Add(this.caCertificate.Value);
+ validationChain.ChainPolicy.ExtraStore.AddRange(chain.ChainElements.OfType().Select(element => element.Certificate).ToArray());
+ var isVerified = validationChain.Build(certificate as X509Certificate2 ?? new X509Certificate2(certificate));
+ var isSignedByExpectedRoot = validationChain.ChainElements[validationChain.ChainElements.Count - 1].Certificate.RawData.SequenceEqual(this.caCertificate.Value.RawData);
+ var isSuccess = isVerified && isSignedByExpectedRoot;
+ return isSuccess;
+ }
+ }
+ }
+}
diff --git a/src/Testcontainers/Configurations/Credentials/TlsCredentials.cs b/src/Testcontainers/Configurations/Credentials/TlsCredentials.cs
new file mode 100644
index 000000000..cb8d90d7c
--- /dev/null
+++ b/src/Testcontainers/Configurations/Credentials/TlsCredentials.cs
@@ -0,0 +1,31 @@
+namespace DotNet.Testcontainers.Configurations.Credentials
+{
+ using System.Net;
+ using System.Net.Http;
+ using System.Net.Security;
+ using Docker.DotNet;
+ using JetBrains.Annotations;
+ using Microsoft.Net.Http.Client;
+
+ public class TlsCredentials : Credentials
+ {
+ public TlsCredentials([CanBeNull] RemoteCertificateValidationCallback serverCertificateValidationCallback)
+ {
+ this.ServerCertificateValidationCallback = serverCertificateValidationCallback;
+ }
+
+ public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get; set; }
+
+ public override bool IsTlsCredentials()
+ {
+ return true;
+ }
+
+ public override HttpMessageHandler GetHandler(HttpMessageHandler innerHandler)
+ {
+ var handler = (ManagedHandler)innerHandler;
+ handler.ServerCertificateValidationCallback = this.ServerCertificateValidationCallback ?? ServicePointManager.ServerCertificateValidationCallback;
+ return handler;
+ }
+ }
+}
diff --git a/src/Testcontainers/Configurations/DockerEndpointAuthenticationConfiguration.cs b/src/Testcontainers/Configurations/DockerEndpointAuthenticationConfiguration.cs
index 10c63b35b..67767e6bb 100644
--- a/src/Testcontainers/Configurations/DockerEndpointAuthenticationConfiguration.cs
+++ b/src/Testcontainers/Configurations/DockerEndpointAuthenticationConfiguration.cs
@@ -12,11 +12,16 @@
/// Initializes a new instance of the struct.
///
/// The Docker API endpoint.
- public DockerEndpointAuthenticationConfiguration(Uri endpoint)
+ /// The Docker API authentication credentials.
+ public DockerEndpointAuthenticationConfiguration(Uri endpoint, Docker.DotNet.Credentials credentials = null)
{
+ this.Credentials = credentials;
this.Endpoint = endpoint;
}
+ ///
+ public Docker.DotNet.Credentials Credentials { get; }
+
///
public Uri Endpoint { get; }
@@ -24,7 +29,7 @@ public DockerEndpointAuthenticationConfiguration(Uri endpoint)
public DockerClientConfiguration GetDockerClientConfiguration(Guid sessionId = default)
{
var defaultHttpRequestHeaders = new ReadOnlyDictionary(new Dictionary { { "x-tc-sid", sessionId.ToString("D") } });
- return new DockerClientConfiguration(this.Endpoint, defaultHttpRequestHeaders: defaultHttpRequestHeaders);
+ return new DockerClientConfiguration(this.Endpoint, this.Credentials, defaultHttpRequestHeaders: defaultHttpRequestHeaders);
}
}
}
diff --git a/src/Testcontainers/Configurations/IDockerEndpointAuthenticationConfiguration.cs b/src/Testcontainers/Configurations/IDockerEndpointAuthenticationConfiguration.cs
index 3a8a48508..9f0b63f13 100644
--- a/src/Testcontainers/Configurations/IDockerEndpointAuthenticationConfiguration.cs
+++ b/src/Testcontainers/Configurations/IDockerEndpointAuthenticationConfiguration.cs
@@ -15,6 +15,12 @@ public interface IDockerEndpointAuthenticationConfiguration
[NotNull]
Uri Endpoint { get; }
+ ///
+ /// Gets the Docker API authentication credentials.
+ ///
+ [CanBeNull]
+ Docker.DotNet.Credentials Credentials { get; }
+
///
/// Gets the Docker client configuration.
///
diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs
index 2a959bd7f..1f963fff5 100644
--- a/src/Testcontainers/Configurations/TestcontainersSettings.cs
+++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs
@@ -21,7 +21,13 @@ public static class TestcontainersSettings
private static readonly IDockerImage RyukContainerImage = new DockerImage("testcontainers/ryuk:0.3.4");
private static readonly IDockerEndpointAuthenticationConfiguration DockerEndpointAuthConfig =
- new IDockerEndpointAuthenticationProvider[] { new EnvironmentEndpointAuthenticationProvider(), new NpipeEndpointAuthenticationProvider(), new UnixEndpointAuthenticationProvider() }
+ new IDockerEndpointAuthenticationProvider[]
+ {
+ new TlsEndpointAuthenticationProvider(),
+ new EnvironmentEndpointAuthenticationProvider(),
+ new NpipeEndpointAuthenticationProvider(),
+ new UnixEndpointAuthenticationProvider(),
+ }
.AsParallel()
.Where(authProvider => authProvider.IsApplicable())
.Where(authProvider => authProvider.IsAvailable())
diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs
index eeec3102d..ca0a3649a 100644
--- a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs
+++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs
@@ -10,6 +10,7 @@ namespace DotNet.Testcontainers.Tests.Unit
public sealed class DockerEndpointAuthenticationProviderTest
{
private const string DockerHost = "tcp://127.0.0.1:2375";
+ private const string DockerTlsHost = "tcp://127.0.0.1:2376";
[Theory]
[ClassData(typeof(AuthProviderTestData))]
@@ -35,7 +36,12 @@ public AuthProviderTestData()
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var defaultConfiguration = new PropertiesFileConfiguration(Array.Empty());
- var dockerHostConfiguration = new PropertiesFileConfiguration(new[] { "docker.host=" + DockerHost });
+ var dockerHostConfiguration = new PropertiesFileConfiguration(new[] { $"docker.host={DockerHost}" });
+ var dockerTlsConfiguration = new PropertiesFileConfiguration(new[] { "docker.tls=true" });
+ this.Add(new object[] { new TlsEndpointAuthenticationProvider(defaultConfiguration), false });
+ this.Add(new object[] { new TlsEndpointAuthenticationProvider(dockerTlsConfiguration), true });
+ this.Add(new object[] { new TlsEndpointAuthenticationProvider(Array.Empty()), false });
+ this.Add(new object[] { new TlsEndpointAuthenticationProvider(defaultConfiguration, dockerTlsConfiguration), true });
this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(defaultConfiguration), false });
this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(dockerHostConfiguration), true });
this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(Array.Empty()), false });
@@ -49,7 +55,9 @@ private sealed class AuthConfigTestData : List