diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c5bede06..b2fafefb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- 370 Add protected Docker daemon socket support (@vlaskal) - 421 Add Azurite module (@vlaskal) - 421 Add Cosmos DB Linux Emulator (@Yeseh, @ktjn) - 504 Add Elasticsearch module (@chertby) diff --git a/Packages.props b/Packages.props index 9c9761afb..72027045e 100644 --- a/Packages.props +++ b/Packages.props @@ -6,8 +6,10 @@ + + diff --git a/src/Testcontainers/Builders/MTlsEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/MTlsEndpointAuthenticationProvider.cs new file mode 100644 index 000000000..1da666f3b --- /dev/null +++ b/src/Testcontainers/Builders/MTlsEndpointAuthenticationProvider.cs @@ -0,0 +1,95 @@ +namespace DotNet.Testcontainers.Builders +{ + using System; + using System.IO; + using System.Linq; + using System.Security.Cryptography.X509Certificates; + using Docker.DotNet.X509; + using DotNet.Testcontainers.Configurations; + using Org.BouncyCastle.Crypto; + using Org.BouncyCastle.OpenSsl; + using Org.BouncyCastle.Pkcs; + using Org.BouncyCastle.Security; + using Org.BouncyCastle.X509; + + /// + internal sealed class MTlsEndpointAuthenticationProvider : TlsEndpointAuthenticationProvider + { + private static readonly X509CertificateParser CertificateParser = new X509CertificateParser(); + + /// + /// Initializes a new instance of the class. + /// + public MTlsEndpointAuthenticationProvider() + : this(PropertiesFileConfiguration.Instance, EnvironmentConfiguration.Instance) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A list of custom configurations. + public MTlsEndpointAuthenticationProvider(params ICustomConfiguration[] customConfigurations) + : base(customConfigurations) + { + } + + /// + public override bool IsApplicable() + { + var certificatesFiles = new[] { ClientCertificateFileName, ClientCertificateKeyFileName }; + return this.TlsEnabled && this.TlsVerifyEnabled && certificatesFiles.Select(fileName => Path.Combine(this.CertificatesDirectoryPath, fileName)).All(File.Exists); + } + + /// + public override IDockerEndpointAuthenticationConfiguration GetAuthConfig() + { + var credentials = new CertificateCredentials(this.GetClientCertificate()); + credentials.ServerCertificateValidationCallback = this.ServerCertificateValidationCallback; + return new DockerEndpointAuthenticationConfiguration(this.DockerEngine, credentials); + } + + /// + protected override X509Certificate2 GetClientCertificate() + { + var clientCertificateFilePath = Path.Combine(this.CertificatesDirectoryPath, ClientCertificateFileName); + var clientCertificateKeyFilePath = Path.Combine(this.CertificatesDirectoryPath, ClientCertificateKeyFileName); + return CreateFromPemFile(clientCertificateFilePath, clientCertificateKeyFilePath); + } + + private static X509Certificate2 CreateFromPemFile(string certPemFilePath, string keyPemFilePath) + { + if (!File.Exists(certPemFilePath)) + { + throw new FileNotFoundException(certPemFilePath); + } + + if (!File.Exists(keyPemFilePath)) + { + throw new FileNotFoundException(keyPemFilePath); + } + + using (var keyPairStream = new StreamReader(keyPemFilePath)) + { + var store = new Pkcs12StoreBuilder().Build(); + + var certificate = CertificateParser.ReadCertificate(File.ReadAllBytes(certPemFilePath)); + + var password = Guid.NewGuid().ToString("D"); + + var keyPair = (AsymmetricCipherKeyPair)new PemReader(keyPairStream).ReadObject(); + + var certificateEntry = new X509CertificateEntry(certificate); + + var keyEntry = new AsymmetricKeyEntry(keyPair.Private); + store.SetKeyEntry(certificate.SubjectDN + "_key", keyEntry, new[] { certificateEntry }); + + using (var certificateStream = new MemoryStream()) + { + store.Save(certificateStream, password.ToCharArray(), new SecureRandom()); + return new X509Certificate2(Pkcs12Utilities.ConvertToDefiniteLength(certificateStream.ToArray()), password); + } + } + } + } +} diff --git a/src/Testcontainers/Builders/TlsEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/TlsEndpointAuthenticationProvider.cs new file mode 100644 index 000000000..e9740b007 --- /dev/null +++ b/src/Testcontainers/Builders/TlsEndpointAuthenticationProvider.cs @@ -0,0 +1,115 @@ +namespace DotNet.Testcontainers.Builders +{ + using System; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Security; + using System.Security.Cryptography.X509Certificates; + using DotNet.Testcontainers.Configurations; + + /// + internal class TlsEndpointAuthenticationProvider : DockerEndpointAuthenticationProvider + { + protected const string CaCertificateFileName = "ca.pem"; + + protected const string ClientCertificateFileName = "cert.pem"; + + protected const string ClientCertificateKeyFileName = "key.pem"; + + /// + /// 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) + : this(customConfigurations + .OrderByDescending(item => item.GetDockerTlsVerify()) + .ThenByDescending(item => item.GetDockerTls()) + .DefaultIfEmpty(new PropertiesFileConfiguration(Array.Empty())) + .First()) + { + } + + private TlsEndpointAuthenticationProvider(ICustomConfiguration customConfiguration) + { + this.TlsEnabled = customConfiguration.GetDockerTls() || customConfiguration.GetDockerTlsVerify(); + this.TlsVerifyEnabled = customConfiguration.GetDockerTlsVerify(); + this.CertificatesDirectoryPath = customConfiguration.GetDockerCertPath() ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".docker"); + this.DockerEngine = customConfiguration.GetDockerHost() ?? new Uri("tcp://localhost:2376"); + } + + protected bool TlsEnabled { get; } + + protected bool TlsVerifyEnabled { get; } + + protected string CertificatesDirectoryPath { get; } + + protected Uri DockerEngine { get; } + + /// + public override bool IsApplicable() + { + return this.TlsEnabled; + } + + /// + public override IDockerEndpointAuthenticationConfiguration GetAuthConfig() + { + var credentials = new TlsCredentials(); + credentials.ServerCertificateValidationCallback = this.ServerCertificateValidationCallback; + return new DockerEndpointAuthenticationConfiguration(this.DockerEngine, credentials); + } + + /// + /// Gets the root certificate authority (CA). + /// + /// The root certificate authority (CA). + protected virtual X509Certificate2 GetCaCertificate() + { + return new X509Certificate2(Path.Combine(this.CertificatesDirectoryPath, CaCertificateFileName)); + } + + /// + /// Gets the client certificate. + /// + /// The client certificate. + protected virtual X509Certificate2 GetClientCertificate() + { + return null; + } + + /// + protected virtual bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + switch (sslPolicyErrors) + { + case SslPolicyErrors.None: + return true; + case SslPolicyErrors.RemoteCertificateNameMismatch: + case SslPolicyErrors.RemoteCertificateNotAvailable: + return false; + case SslPolicyErrors.RemoteCertificateChainErrors: + default: + using (var caCertificate = this.GetCaCertificate()) + { + var validationChain = new X509Chain(); + validationChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + validationChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + validationChain.ChainPolicy.ExtraStore.Add(caCertificate); + validationChain.ChainPolicy.ExtraStore.AddRange(chain.ChainElements.OfType().Select(element => element.Certificate).ToArray()); + var isVerified = validationChain.Build(new X509Certificate2(certificate)); + var isSignedByExpectedRoot = validationChain.ChainElements[validationChain.ChainElements.Count - 1].Certificate.RawData.SequenceEqual(caCertificate.RawData); + return isVerified && isSignedByExpectedRoot; + } + } + } + } +} diff --git a/src/Testcontainers/Clients/DockerSystemOperations.cs b/src/Testcontainers/Clients/DockerSystemOperations.cs index 77fabcfaa..70362df7b 100644 --- a/src/Testcontainers/Clients/DockerSystemOperations.cs +++ b/src/Testcontainers/Clients/DockerSystemOperations.cs @@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Clients using System; using System.Threading; using System.Threading.Tasks; + using Docker.DotNet.Models; using DotNet.Testcontainers.Configurations; using Microsoft.Extensions.Logging; @@ -19,5 +20,10 @@ public async Task GetIsWindowsEngineEnabled(CancellationToken ct = default return (await this.Docker.System.GetSystemInfoAsync(ct) .ConfigureAwait(false)).OperatingSystem.Contains("Windows"); } + + public Task GetVersion(CancellationToken ct = default) + { + return this.Docker.System.GetVersionAsync(ct); + } } } diff --git a/src/Testcontainers/Clients/IDockerSystemOperations.cs b/src/Testcontainers/Clients/IDockerSystemOperations.cs index 39ad21a74..6f11ec298 100644 --- a/src/Testcontainers/Clients/IDockerSystemOperations.cs +++ b/src/Testcontainers/Clients/IDockerSystemOperations.cs @@ -2,9 +2,12 @@ namespace DotNet.Testcontainers.Clients { using System.Threading; using System.Threading.Tasks; + using Docker.DotNet.Models; internal interface IDockerSystemOperations { Task GetIsWindowsEngineEnabled(CancellationToken ct = default); + + Task GetVersion(CancellationToken ct = default); } } diff --git a/src/Testcontainers/Configurations/DockerEndpointAuthenticationConfiguration.cs b/src/Testcontainers/Configurations/DockerEndpointAuthenticationConfiguration.cs index 10c63b35b..cb81865ea 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, Credentials credentials = null) { + this.Credentials = credentials; this.Endpoint = endpoint; } + /// + public 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..ab1851c3e 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 credentials. + /// + [CanBeNull] + Credentials Credentials { get; } + /// /// Gets the Docker client configuration. /// diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs index 2a959bd7f..3533ab78b 100644 --- a/src/Testcontainers/Configurations/TestcontainersSettings.cs +++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs @@ -21,7 +21,14 @@ 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 MTlsEndpointAuthenticationProvider(), + new TlsEndpointAuthenticationProvider(), + new EnvironmentEndpointAuthenticationProvider(), + new NpipeEndpointAuthenticationProvider(), + new UnixEndpointAuthenticationProvider(), + } .AsParallel() .Where(authProvider => authProvider.IsApplicable()) .Where(authProvider => authProvider.IsAvailable()) diff --git a/src/Testcontainers/Configurations/TlsCredentials.cs b/src/Testcontainers/Configurations/TlsCredentials.cs new file mode 100644 index 000000000..3a30e6efa --- /dev/null +++ b/src/Testcontainers/Configurations/TlsCredentials.cs @@ -0,0 +1,27 @@ +namespace DotNet.Testcontainers.Configurations +{ + using System.Net; + using System.Net.Http; + using Docker.DotNet.X509; + using Microsoft.Net.Http.Client; + + internal sealed class TlsCredentials : CertificateCredentials + { + public TlsCredentials() + : base(null) + { + } + + 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/Testcontainers.csproj b/src/Testcontainers/Testcontainers.csproj index 1c67783b2..ad657e5bb 100644 --- a/src/Testcontainers/Testcontainers.csproj +++ b/src/Testcontainers/Testcontainers.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1 @@ -9,8 +9,10 @@ + + diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTlsFixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTlsFixture.cs new file mode 100644 index 000000000..6dbba714c --- /dev/null +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTlsFixture.cs @@ -0,0 +1,27 @@ +namespace DotNet.Testcontainers.Tests.Fixtures +{ + using System.Collections.Generic; + using DotNet.Testcontainers.Builders; + using DotNet.Testcontainers.Containers; + using JetBrains.Annotations; + + [UsedImplicitly] + public sealed class DockerMTlsFixture : ProtectDockerDaemonSocket + { + public DockerMTlsFixture() + : base(new TestcontainersBuilder()) + { + } + + public override IList CustomProperties + { + get + { + var customProperties = base.CustomProperties; + customProperties.Add("docker.tls=false"); + customProperties.Add("docker.tls.verify=true"); + return customProperties; + } + } + } +} diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs new file mode 100644 index 000000000..1dc8d3f4a --- /dev/null +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs @@ -0,0 +1,28 @@ +namespace DotNet.Testcontainers.Tests.Fixtures +{ + using System.Collections.Generic; + using DotNet.Testcontainers.Builders; + using DotNet.Testcontainers.Containers; + using JetBrains.Annotations; + + [UsedImplicitly] + public sealed class DockerTlsFixture : ProtectDockerDaemonSocket + { + public DockerTlsFixture() + : base(new TestcontainersBuilder() + .WithCommand("--tlsverify=false")) + { + } + + public override IList CustomProperties + { + get + { + var customProperties = base.CustomProperties; + customProperties.Add("docker.tls=true"); + customProperties.Add("docker.tls.verify=false"); + return customProperties; + } + } + } +} diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/AzuriteFixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/AzuriteFixture.cs index 69a752589..84cbab413 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/AzuriteFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/AzuriteFixture.cs @@ -11,9 +11,6 @@ namespace DotNet.Testcontainers.Tests.Fixtures public static class AzuriteFixture { - // We cannot use `Path.GetTempPath()` on macOS, see: https://github.com/common-workflow-language/cwltool/issues/328. - private static readonly string TempDir = Environment.GetEnvironmentVariable("AGENT_TEMPDIRECTORY") ?? Directory.GetCurrentDirectory(); - [UsedImplicitly] public class AzuriteDefaultFixture : IAsyncLifetime { @@ -92,7 +89,7 @@ public sealed class AzuriteWithCustomWorkspaceFixture : AzuriteDefaultFixture, I public AzuriteWithCustomWorkspaceFixture() : base(new AzuriteTestcontainerConfiguration { - Location = Path.Combine(TempDir, Guid.NewGuid().ToString("N")), + Location = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D")), }) { if (this.Configuration.Location != null) diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/ProtectDockerDaemonSocket.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/ProtectDockerDaemonSocket.cs new file mode 100644 index 000000000..2a14bf080 --- /dev/null +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/ProtectDockerDaemonSocket.cs @@ -0,0 +1,71 @@ +namespace DotNet.Testcontainers.Tests.Fixtures +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using DotNet.Testcontainers.Builders; + using DotNet.Testcontainers.Configurations; + using DotNet.Testcontainers.Containers; + using DotNet.Testcontainers.Images; + using Xunit; + + public abstract class ProtectDockerDaemonSocket : IAsyncLifetime + { + public const string DockerVersion = "20.10.18"; + + private const string CertsDirectoryName = "certs"; + + private const ushort TlsPort = 2376; + + private readonly string hostCertsDirectoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"), CertsDirectoryName); + + private readonly string containerCertsDirectoryPath = Path.Combine("/", CertsDirectoryName); + + private readonly IDockerImage image = new DockerImage(string.Empty, "docker", DockerVersion + "-dind"); + + private readonly ITestcontainersContainer container; + + protected ProtectDockerDaemonSocket(ITestcontainersBuilder containerConfiguration) + { + this.container = containerConfiguration + .WithImage(this.image) + .WithPrivileged(true) + .WithExposedPort(TlsPort) + .WithPortBinding(TlsPort, true) + .WithBindMount(this.hostCertsDirectoryPath, this.containerCertsDirectoryPath, AccessMode.ReadWrite) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(TlsPort)) + .Build(); + } + + public virtual IList CustomProperties + { + get + { + var customProperties = new List(); + customProperties.Add($"docker.host={this.TcpEndpoint}"); + customProperties.Add($"docker.cert.path={Path.Combine(this.hostCertsDirectoryPath, "client")}"); + return customProperties; + } + } + + private Uri TcpEndpoint + { + get + { + return new UriBuilder("tcp", this.container.Hostname, this.container.GetMappedPublicPort(TlsPort)).Uri; + } + } + + public Task InitializeAsync() + { + _ = Directory.CreateDirectory(this.hostCertsDirectoryPath); + return this.container.StartAsync(); + } + + public Task DisposeAsync() + { + return this.container.DisposeAsync().AsTask(); + } + } +} diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs index eeec3102d..ba9d9ad36 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerEndpointAuthenticationProviderTest.cs @@ -2,6 +2,7 @@ namespace DotNet.Testcontainers.Tests.Unit { using System; using System.Collections.Generic; + using System.IO; using System.Runtime.InteropServices; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Configurations; @@ -11,6 +12,22 @@ public sealed class DockerEndpointAuthenticationProviderTest { private const string DockerHost = "tcp://127.0.0.1:2375"; + private const string DockerTlsHost = "tcp://127.0.0.1:2376"; + + private static readonly string CertificatesDirectoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D")); + + private static readonly ICustomConfiguration DockerHostConfiguration = new PropertiesFileConfiguration(new[] { "docker.host=" + DockerHost }); + + private static readonly ICustomConfiguration DockerTlsHostConfiguration = new PropertiesFileConfiguration(new[] { "docker.host=" + DockerTlsHost }); + + static DockerEndpointAuthenticationProviderTest() + { + _ = Directory.CreateDirectory(CertificatesDirectoryPath); + _ = File.Create(Path.Combine(CertificatesDirectoryPath, "ca.pem")); + _ = File.Create(Path.Combine(CertificatesDirectoryPath, "cert.pem")); + _ = File.Create(Path.Combine(CertificatesDirectoryPath, "key.pem")); + } + [Theory] [ClassData(typeof(AuthProviderTestData))] internal void AuthProviderShouldBeApplicable(IDockerEndpointAuthenticationProvider authProvider, bool isApplicable) @@ -35,11 +52,20 @@ public AuthProviderTestData() { var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var defaultConfiguration = new PropertiesFileConfiguration(Array.Empty()); - var dockerHostConfiguration = new PropertiesFileConfiguration(new[] { "docker.host=" + DockerHost }); + var dockerTlsConfiguration = new PropertiesFileConfiguration("docker.tls=true", $"docker.cert.path={CertificatesDirectoryPath}"); + var dockerMTlsConfiguration = new PropertiesFileConfiguration("docker.tls.verify=true", $"docker.cert.path={CertificatesDirectoryPath}"); + this.Add(new object[] { new MTlsEndpointAuthenticationProvider(defaultConfiguration), false }); + this.Add(new object[] { new MTlsEndpointAuthenticationProvider(dockerMTlsConfiguration), true }); + this.Add(new object[] { new MTlsEndpointAuthenticationProvider(Array.Empty()), false }); + this.Add(new object[] { new MTlsEndpointAuthenticationProvider(defaultConfiguration, dockerMTlsConfiguration), 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(DockerHostConfiguration), true }); this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(Array.Empty()), false }); - this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(defaultConfiguration, dockerHostConfiguration), true }); + this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(defaultConfiguration, DockerHostConfiguration), true }); this.Add(new object[] { new NpipeEndpointAuthenticationProvider(), isWindows }); this.Add(new object[] { new UnixEndpointAuthenticationProvider(), !isWindows }); } @@ -49,8 +75,8 @@ private sealed class AuthConfigTestData : List { public AuthConfigTestData() { - var dockerHostConfiguration = new PropertiesFileConfiguration(new[] { "docker.host=" + DockerHost }); - this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(dockerHostConfiguration).GetAuthConfig(), new Uri(DockerHost) }); + this.Add(new object[] { new TlsEndpointAuthenticationProvider(DockerTlsHostConfiguration).GetAuthConfig(), new Uri(DockerTlsHost) }); + this.Add(new object[] { new EnvironmentEndpointAuthenticationProvider(DockerHostConfiguration).GetAuthConfig(), new Uri(DockerHost) }); this.Add(new object[] { new NpipeEndpointAuthenticationProvider().GetAuthConfig(), new Uri("npipe://./pipe/docker_engine") }); this.Add(new object[] { new UnixEndpointAuthenticationProvider().GetAuthConfig(), new Uri("unix:/var/run/docker.sock") }); } diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs new file mode 100644 index 000000000..e186c35ee --- /dev/null +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs @@ -0,0 +1,69 @@ +namespace DotNet.Testcontainers.Tests.Unit.Containers.Unix +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using DotNet.Testcontainers.Builders; + using DotNet.Testcontainers.Clients; + using DotNet.Testcontainers.Configurations; + using DotNet.Testcontainers.Tests.Fixtures; + using Microsoft.Extensions.Logging.Abstractions; + using Xunit; + + public static class ProtectDockerDaemonSocketTest + { + private static IDockerEndpointAuthenticationConfiguration GetAuthConfig(ProtectDockerDaemonSocket protectDockerDaemonSocket) + { + var customConfiguration = new PropertiesFileConfiguration(protectDockerDaemonSocket.CustomProperties.ToArray()); + return new IDockerEndpointAuthenticationProvider[] { new MTlsEndpointAuthenticationProvider(customConfiguration), new TlsEndpointAuthenticationProvider(customConfiguration) }.First(authProvider => authProvider.IsApplicable()).GetAuthConfig(); + } + + public sealed class MTls : IClassFixture + { + private readonly IDockerEndpointAuthenticationConfiguration authConfig; + + public MTls(DockerMTlsFixture dockerMTlsFixture) + { + this.authConfig = GetAuthConfig(dockerMTlsFixture); + } + + [Fact] + public async Task GetVersionReturnsVersion() + { + // Given + IDockerSystemOperations dockerSystemOperations = new DockerSystemOperations(Guid.Empty, this.authConfig, NullLogger.Instance); + + // When + var version = await dockerSystemOperations.GetVersion() + .ConfigureAwait(false); + + // Then + Assert.Equal(ProtectDockerDaemonSocket.DockerVersion, version.Version); + } + } + + public sealed class Tls : IClassFixture + { + private readonly IDockerEndpointAuthenticationConfiguration authConfig; + + public Tls(DockerTlsFixture dockerTlsFixture) + { + this.authConfig = GetAuthConfig(dockerTlsFixture); + } + + [Fact] + public async Task GetVersionReturnsVersion() + { + // Given + IDockerSystemOperations dockerSystemOperations = new DockerSystemOperations(Guid.Empty, this.authConfig, NullLogger.Instance); + + // When + var version = await dockerSystemOperations.GetVersion() + .ConfigureAwait(false); + + // Then + Assert.Equal(ProtectDockerDaemonSocket.DockerVersion, version.Version); + } + } + } +} diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs index 99998c81d..479509b2f 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/TestcontainersContainerTest.cs @@ -17,8 +17,12 @@ namespace DotNet.Testcontainers.Tests.Unit.Containers.Unix public static class TestcontainersContainerTest { - // We cannot use `Path.GetTempPath()` on macOS, see: https://github.com/common-workflow-language/cwltool/issues/328. - private static readonly string TempDir = Environment.GetEnvironmentVariable("AGENT_TEMPDIRECTORY") ?? Directory.GetCurrentDirectory(); + private static readonly string TempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D")); + + static TestcontainersContainerTest() + { + _ = Directory.CreateDirectory(TempPath); + } [Collection(nameof(Testcontainers))] public sealed class WithConfiguration @@ -225,9 +229,9 @@ public async Task BindMountAndCommand() .WithImage("nginx") .WithEntrypoint("/bin/sh", "-c") .WithCommand($"hostname > /{target}/{file} && tail -f /dev/null") - .WithBindMount(TempDir, $"/{target}") + .WithBindMount(TempPath, $"/{target}") .WithWaitStrategy(Wait.ForUnixContainer() - .UntilFileExists(Path.Combine(TempDir, file))); + .UntilFileExists(Path.Combine(TempPath, file))); await using (ITestcontainersContainer testcontainer = testcontainersBuilder.Build()) { @@ -235,7 +239,7 @@ public async Task BindMountAndCommand() } // Then - Assert.True(File.Exists(Path.Combine(TempDir, file)), $"{file} does not exist."); + Assert.True(File.Exists(Path.Combine(TempPath, file)), $"{file} does not exist."); } [Fact] @@ -254,10 +258,10 @@ public async Task BindMountAndEnvironment() .WithNetworkAliases("Foo") .WithEntrypoint("/bin/sh", "-c", $"printf $dayOfWeek > /{target}/{file} && tail -f /dev/null") .WithEnvironment("dayOfWeek", dayOfWeek) - .WithBindMount(TempDir, $"/{target}") + .WithBindMount(TempPath, $"/{target}") .ConfigureContainer(_ => { }) // https://github.com/testcontainers/testcontainers-dotnet/issues/507. .WithWaitStrategy(Wait.ForUnixContainer() - .UntilFileExists(Path.Combine(TempDir, file))); + .UntilFileExists(Path.Combine(TempPath, file))); await using (ITestcontainersContainer testcontainer = testcontainersBuilder.Build()) { @@ -265,7 +269,7 @@ public async Task BindMountAndEnvironment() } // Then - Assert.Equal(dayOfWeek, await File.ReadAllTextAsync(Path.Combine(TempDir, file))); + Assert.Equal(dayOfWeek, await File.ReadAllTextAsync(Path.Combine(TempPath, file))); } [Fact]