forked from testcontainers/testcontainers-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(testcontainers#370): Added support docker mTLS endpoint
- Loading branch information
Showing
9 changed files
with
245 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
src/Testcontainers/Builders/MTlsEndpointAuthenticationProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
namespace DotNet.Testcontainers.Builders | ||
{ | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text.RegularExpressions; | ||
using Docker.DotNet.X509; | ||
using DotNet.Testcontainers.Configurations; | ||
using Org.BouncyCastle.Asn1; | ||
using Org.BouncyCastle.Asn1.Pkcs; | ||
using Org.BouncyCastle.Crypto; | ||
using Org.BouncyCastle.Crypto.Parameters; | ||
using Org.BouncyCastle.Pkcs; | ||
using Org.BouncyCastle.Security; | ||
using Org.BouncyCastle.X509; | ||
using MSX509 = System.Security.Cryptography.X509Certificates; | ||
|
||
/// <inheritdoc cref="IDockerRegistryAuthenticationProvider" /> | ||
internal sealed class MTlsEndpointAuthenticationProvider : TlsEndpointAuthenticationProvider | ||
{ | ||
private const string DefaultClientCertFileName = "cert.pem"; | ||
private const string DefaultClientKeyFileName = "key.pem"; | ||
private static readonly Regex PemData = new Regex("-----BEGIN (.*)-----(.*)-----END (.*)-----", RegexOptions.Multiline); | ||
|
||
private readonly Lazy<MSX509.X509Certificate2> clientCertificate; | ||
private readonly string dockerClientCertFile; | ||
private readonly string dockerClientKeyFile; | ||
private readonly bool dockerTlsVerifyEnabled; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MTlsEndpointAuthenticationProvider" /> class. | ||
/// </summary> | ||
public MTlsEndpointAuthenticationProvider() | ||
: this(PropertiesFileConfiguration.Instance, EnvironmentConfiguration.Instance) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MTlsEndpointAuthenticationProvider" /> class. | ||
/// </summary> | ||
/// <param name="customConfigurations">A list of custom configurations.</param> | ||
public MTlsEndpointAuthenticationProvider(params ICustomConfiguration[] customConfigurations) | ||
: base(customConfigurations) | ||
{ | ||
this.dockerTlsVerifyEnabled = customConfigurations | ||
.Select(customConfiguration => customConfiguration.GetDockerTlsVerify()) | ||
.Aggregate(false, (x, y) => x || y); | ||
this.dockerClientCertFile = Path.Combine(this.DockerCertPath, DefaultClientCertFileName); | ||
this.dockerClientKeyFile = Path.Combine(this.DockerCertPath, DefaultClientKeyFileName); | ||
this.clientCertificate = new Lazy<MSX509.X509Certificate2>(this.GetClientCertificate); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override bool IsApplicable() | ||
{ | ||
return this.dockerTlsVerifyEnabled; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override IDockerEndpointAuthenticationConfiguration GetAuthConfig() | ||
{ | ||
var certificateCredentials = new CertificateCredentials(this.clientCertificate.Value); | ||
certificateCredentials.ServerCertificateValidationCallback = this.ServerCertificateValidationCallback; | ||
return new DockerEndpointAuthenticationConfiguration(this.DockerEngine, certificateCredentials); | ||
} | ||
|
||
private static MSX509.X509Certificate2 GetCertificateWithKey(X509Certificate certificate, AsymmetricKeyParameter privateKey) | ||
{ | ||
var store = new Pkcs12StoreBuilder() | ||
.SetUseDerEncoding(true) | ||
.Build(); | ||
|
||
var certEntry = new X509CertificateEntry(certificate); | ||
store.SetKeyEntry(certificate.SubjectDN.ToString(), new AsymmetricKeyEntry(privateKey), new[] { certEntry }); | ||
|
||
byte[] pfxBytes; | ||
var password = Guid.NewGuid().ToString("N"); | ||
|
||
using (var stream = new MemoryStream()) | ||
{ | ||
store.Save(stream, password.ToCharArray(), new SecureRandom()); | ||
pfxBytes = stream.ToArray(); | ||
} | ||
|
||
var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes); | ||
|
||
return new MSX509.X509Certificate2(result, password, MSX509.X509KeyStorageFlags.Exportable); | ||
} | ||
|
||
private static X509Certificate ReadPemCert(string pathToPemFile) | ||
{ | ||
var x509CertificateParser = new X509CertificateParser(); | ||
var cert = x509CertificateParser.ReadCertificate(File.ReadAllBytes(pathToPemFile)); | ||
|
||
return cert; | ||
} | ||
|
||
private static AsymmetricKeyParameter ReadPemRsaPrivateKey(string pathToPemFile) | ||
{ | ||
var keyData = File.ReadAllText(pathToPemFile) | ||
.Replace("\n", string.Empty); | ||
|
||
var keyMatch = PemData.Match(keyData); | ||
if (!keyMatch.Success) | ||
{ | ||
throw new NotSupportedException("Not supported key content"); | ||
} | ||
|
||
if (keyMatch.Groups[1].Value != "RSA PRIVATE KEY") | ||
{ | ||
throw new NotSupportedException("Not supported key type. Only RSA PRIVATE KEY is supported."); | ||
} | ||
|
||
var keyContent = keyMatch.Groups[2].Value; | ||
var keyRawData = Convert.FromBase64String(keyContent); | ||
var seq = Asn1Sequence.GetInstance(keyRawData); | ||
|
||
if (seq.Count != 9) | ||
{ | ||
throw new NotSupportedException("Invalid RSA Private Key ASN1 sequence."); | ||
} | ||
|
||
var keyStructure = RsaPrivateKeyStructure.GetInstance(seq); | ||
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(new RsaPrivateCrtKeyParameters(keyStructure)); | ||
var key = PrivateKeyFactory.CreateKey(privateKeyInfo); | ||
|
||
return key; | ||
} | ||
|
||
private MSX509.X509Certificate2 GetClientCertificate() | ||
{ | ||
var certificate = ReadPemCert(this.dockerClientCertFile); | ||
var key = ReadPemRsaPrivateKey(this.dockerClientKeyFile); | ||
|
||
return GetCertificateWithKey(certificate, key); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
tests/Testcontainers.Tests/Fixtures/Builders/MTlsEndpointAuthenticationProviderFixture.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
namespace DotNet.Testcontainers.Tests.Fixtures.Builders | ||
{ | ||
using System; | ||
using System.IO; | ||
using System.Threading.Tasks; | ||
using DotNet.Testcontainers.Builders; | ||
using DotNet.Testcontainers.Clients; | ||
using DotNet.Testcontainers.Configurations; | ||
using DotNet.Testcontainers.Containers; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
using Xunit; | ||
|
||
public class MTlsEndpointAuthenticationProviderFixture : IAsyncLifetime | ||
{ | ||
private const string CertsDirectoryName = "certs"; | ||
private static readonly string ContainerCertDirectoryPath = Path.Combine("/", CertsDirectoryName); | ||
private static readonly string HostCertDirectoryPath = Path.Combine(Path.GetTempPath(), CertsDirectoryName); | ||
|
||
private readonly ITestcontainersContainer mTlsContainer = new TestcontainersBuilder<TestcontainersContainer>() | ||
.WithImage("docker:20.10-dind") | ||
.WithPrivileged(true) | ||
.WithEnvironment("DOCKER_CERT_PATH", ContainerCertDirectoryPath) | ||
.WithEnvironment("DOCKER_TLS_CERTDIR", ContainerCertDirectoryPath) | ||
.WithEnvironment("DOCKER_TLS", "1") | ||
.WithEnvironment("DOCKER_TLS_VERIFY", "1") | ||
.WithBindMount(HostCertDirectoryPath, ContainerCertDirectoryPath, AccessMode.ReadWrite) | ||
.WithPortBinding(2376, true) | ||
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(2376)) | ||
.Build(); | ||
|
||
internal DockerSystemOperations DockerSystemOperations { get; private set; } | ||
|
||
public async Task InitializeAsync() | ||
{ | ||
await this.mTlsContainer.StartAsync(); | ||
var propertiesFileConfiguration = new PropertiesFileConfiguration( | ||
$"docker.host=tcp://localhost:{this.mTlsContainer.GetMappedPublicPort(2376)}", | ||
"docker.tls=true", | ||
$"docker.cert.path={Path.Combine(HostCertDirectoryPath, "client")}"); | ||
var mTlsEndpointAuthenticationProvider = new MTlsEndpointAuthenticationProvider(propertiesFileConfiguration); | ||
this.DockerSystemOperations = new DockerSystemOperations(Guid.NewGuid(), mTlsEndpointAuthenticationProvider.GetAuthConfig(), NullLogger.Instance); | ||
} | ||
|
||
public Task DisposeAsync() | ||
{ | ||
return this.mTlsContainer.DisposeAsync().AsTask(); | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
tests/Testcontainers.Tests/Unit/Builders/MTlsEndpointAuthenticationProviderTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
namespace DotNet.Testcontainers.Tests.Unit | ||
{ | ||
using System.Threading.Tasks; | ||
using DotNet.Testcontainers.Tests.Fixtures.Builders; | ||
using Xunit; | ||
|
||
public class MTlsEndpointAuthenticationProviderTest : IClassFixture<MTlsEndpointAuthenticationProviderFixture> | ||
{ | ||
private readonly MTlsEndpointAuthenticationProviderFixture fixture; | ||
|
||
public MTlsEndpointAuthenticationProviderTest(MTlsEndpointAuthenticationProviderFixture fixture) | ||
{ | ||
this.fixture = fixture; | ||
} | ||
|
||
[Fact] | ||
public async Task MTlsEndpointAuthenticationProviderIsAbleProvideDockerEndpointConnectWithMTlsConfiguration() | ||
{ | ||
var versionResponse = await this.fixture.DockerSystemOperations.GetVersion(); | ||
|
||
Assert.NotNull(versionResponse); | ||
Assert.Contains(versionResponse.Components, x => x.Name.ToLowerInvariant() == "engine"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters