Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for RSA private key (RsaPrivateCrtKeyParameters) TLS authentication with protected Docker daemon sockets #978

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/Testcontainers/Builders/MTlsEndpointAuthenticationProvider.cs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ namespace DotNet.Testcontainers.Builders
using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
@@ -79,11 +80,13 @@ private static X509Certificate2 CreateFromPemFile(string certPemFilePath, string

var password = Guid.NewGuid().ToString("D");

var keyPair = (AsymmetricCipherKeyPair)new PemReader(keyPairStream).ReadObject();
var keyObject = new PemReader(keyPairStream).ReadObject();

var certificateEntry = new X509CertificateEntry(certificate);

var keyEntry = new AsymmetricKeyEntry(keyPair.Private);
var keyParameter = ResolveKeyParameter(keyObject);

var keyEntry = new AsymmetricKeyEntry(keyParameter);
store.SetKeyEntry(certificate.SubjectDN + "_key", keyEntry, new[] { certificateEntry });

using (var certificateStream = new MemoryStream())
@@ -93,5 +96,18 @@ private static X509Certificate2 CreateFromPemFile(string certPemFilePath, string
}
}
}

private static AsymmetricKeyParameter ResolveKeyParameter(object keyObject)
{
switch (keyObject)
{
case AsymmetricCipherKeyPair ackp:
return ackp.Private;
case RsaPrivateCrtKeyParameters rpckp:
return rpckp;
default:
throw new ArgumentOutOfRangeException(nameof(keyObject), $"Unsupported asymmetric key entry encountered while trying to resolve key from input object '{keyObject.GetType()}'.");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -2,13 +2,11 @@ namespace DotNet.Testcontainers.Tests.Fixtures
{
using System.Collections.Generic;
using DotNet.Testcontainers.Builders;
using JetBrains.Annotations;

[UsedImplicitly]
public sealed class DockerMTlsFixture : ProtectDockerDaemonSocket
public abstract class DockerMTls : ProtectDockerDaemonSocket
{
public DockerMTlsFixture()
: base(new ContainerBuilder())
public DockerMTls(string dockerImageVersion)
: base(new ContainerBuilder(), dockerImageVersion)
{
}

Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ public sealed class DockerTlsFixture : ProtectDockerDaemonSocket
{
public DockerTlsFixture()
: base(new ContainerBuilder()
.WithCommand("--tlsverify=false"))
.WithCommand("--tlsverify=false"), "20.10.18")
{
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace DotNet.Testcontainers.Tests.Fixtures
{
using JetBrains.Annotations;

[UsedImplicitly]
public sealed class OpenSsl1_1_1Fixture : DockerMTls
{
public OpenSsl1_1_1Fixture() : base("20.10.18")
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace DotNet.Testcontainers.Tests.Fixtures
{
using JetBrains.Annotations;

[UsedImplicitly]
public sealed class OpenSsl3_1Fixture : DockerMTls
{
public OpenSsl3_1Fixture() : base("24.0.5")
{
}
}
}
Original file line number Diff line number Diff line change
@@ -8,12 +8,11 @@ namespace DotNet.Testcontainers.Tests.Fixtures
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using Org.BouncyCastle.OpenSsl;
using Xunit;

public abstract class ProtectDockerDaemonSocket : IAsyncLifetime
{
public const string DockerVersion = "20.10.18";

private const string CertsDirectoryName = "certs";

private const ushort TlsPort = 2376;
@@ -22,14 +21,12 @@ public abstract class ProtectDockerDaemonSocket : IAsyncLifetime

private readonly string _containerCertsDirectoryPath = Path.Combine("/", CertsDirectoryName);

private readonly IImage _image = new DockerImage(string.Empty, "docker", DockerVersion + "-dind");

private readonly IContainer _container;

protected ProtectDockerDaemonSocket(ContainerBuilder containerConfiguration)
protected ProtectDockerDaemonSocket(ContainerBuilder containerConfiguration, string dockerImageVersion)
{
_container = containerConfiguration
.WithImage(_image)
.WithImage(new DockerImage(string.Empty, "docker", dockerImageVersion + "-dind"))
.WithPrivileged(true)
.WithPortBinding(TlsPort, true)
.WithBindMount(_hostCertsDirectoryPath, _containerCertsDirectoryPath, AccessMode.ReadWrite)
@@ -42,17 +39,28 @@ public virtual IList<string> CustomProperties
get
{
var customProperties = new List<string>();
customProperties.Add($"docker.host={TcpEndpoint}");
customProperties.Add($"docker.host={new UriBuilder("tcp", _container.Hostname, _container.GetMappedPublicPort(TlsPort))}");
customProperties.Add($"docker.cert.path={Path.Combine(_hostCertsDirectoryPath, "client")}");
return customProperties;
}
}

private Uri TcpEndpoint
public IImage Image
{
get
{
return _container.Image;
}
}

public object TlsKey
{
get
{
return new UriBuilder("tcp", _container.Hostname, _container.GetMappedPublicPort(TlsPort)).Uri;
using (var tlsKeyStream = new StreamReader(Path.Combine(_hostCertsDirectoryPath, "client", "key.pem")))
{
return new PemReader(tlsKeyStream).ReadObject();
}
}
}

Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@ namespace DotNet.Testcontainers.Tests.Unit
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Tests.Fixtures;
using Microsoft.Extensions.Logging.Abstractions;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Xunit;

public static class ProtectDockerDaemonSocketTest
@@ -18,12 +20,43 @@ private static IDockerEndpointAuthenticationConfiguration GetAuthConfig(ProtectD
return new IDockerEndpointAuthenticationProvider[] { new MTlsEndpointAuthenticationProvider(customConfiguration), new TlsEndpointAuthenticationProvider(customConfiguration) }.First(authProvider => authProvider.IsApplicable()).GetAuthConfig();
}

public sealed class MTls : IClassFixture<DockerMTlsFixture>
public sealed class MTlsOpenSsl1_1_1 : IClassFixture<OpenSsl1_1_1Fixture>
{
private readonly ProtectDockerDaemonSocket _fixture;

private readonly IDockerEndpointAuthenticationConfiguration _authConfig;

public MTlsOpenSsl1_1_1(OpenSsl1_1_1Fixture dockerMTlsFixture)
{
_fixture = dockerMTlsFixture;
_authConfig = GetAuthConfig(dockerMTlsFixture);
}

[Fact]
public async Task GetVersionReturnsVersion()
{
// Given
var client = new TestcontainersClient(Guid.Empty, _authConfig, NullLogger.Instance);

// When
var version = await client.System.GetVersionAsync()
.ConfigureAwait(false);

// Then
Assert.StartsWith(version.Version, _fixture.Image.Tag);
Assert.IsType<AsymmetricCipherKeyPair>(_fixture.TlsKey);
}
}

public sealed class MTlsOpenSsl3_1 : IClassFixture<OpenSsl3_1Fixture>
{
private readonly ProtectDockerDaemonSocket _fixture;

private readonly IDockerEndpointAuthenticationConfiguration _authConfig;

public MTls(DockerMTlsFixture dockerMTlsFixture)
public MTlsOpenSsl3_1(OpenSsl3_1Fixture dockerMTlsFixture)
{
_fixture = dockerMTlsFixture;
_authConfig = GetAuthConfig(dockerMTlsFixture);
}

@@ -38,16 +71,20 @@ public async Task GetVersionReturnsVersion()
.ConfigureAwait(false);

// Then
Assert.Equal(ProtectDockerDaemonSocket.DockerVersion, version.Version);
Assert.StartsWith(version.Version, _fixture.Image.Tag);
Assert.IsType<RsaPrivateCrtKeyParameters>(_fixture.TlsKey);
}
}

public sealed class Tls : IClassFixture<DockerTlsFixture>
{
private readonly ProtectDockerDaemonSocket _fixture;

private readonly IDockerEndpointAuthenticationConfiguration _authConfig;

public Tls(DockerTlsFixture dockerTlsFixture)
{
_fixture = dockerTlsFixture;
_authConfig = GetAuthConfig(dockerTlsFixture);
}

@@ -62,7 +99,7 @@ public async Task GetVersionReturnsVersion()
.ConfigureAwait(false);

// Then
Assert.Equal(ProtectDockerDaemonSocket.DockerVersion, version.Version);
Assert.StartsWith(version.Version, _fixture.Image.Tag);
}
}
}