From 94064cb58f6e8fb919033fd70c5201579e434d57 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Tue, 7 Jul 2026 08:52:30 +0100 Subject: [PATCH] Add integration test certificate management for HTTPS Introduce IntegrationTestCertificate helper to generate, install, and manage a self-signed certificate for secure HTTPS communication in integration tests. Refactor DockerHelper to use this certificate, update container setup to mount and configure certs, and ensure proper cleanup after tests. Improve assertion messages and resource management in test steps. --- .../IntegrationTestCertificate.cs | 128 ++++++++++++++++ .../SecurityServiceSteps.cs | 4 +- .../Common/DockerHelper.cs | 144 +++++++++++------- .../Common/GenericSteps.cs | 9 +- .../Common/DockerHelper.cs | 72 +++++++-- .../Common/GenericSteps.cs | 9 +- 6 files changed, 295 insertions(+), 71 deletions(-) create mode 100644 SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs diff --git a/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs b/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs new file mode 100644 index 00000000..27e9781d --- /dev/null +++ b/SecurityService.IntegrationTesting.Helpers/IntegrationTestCertificate.cs @@ -0,0 +1,128 @@ +using System.Net; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace SecurityService.IntegrationTesting.Helpers; + +public sealed class IntegrationTestCertificate : IDisposable +{ + private const string PasswordValue = "password"; + private const string ServerCertificateFileName = "aspnetapp-web-api.pfx"; + private const string RootCertificateFileName = "aspnetapp-root-cert.cer"; + private const string ContainerCertificateDirectoryPath = "/app/test-certs"; + private const string RootSubjectName = "CN=SecurityServiceIntegrationTestRoot"; + private const string LocalhostName = "localhost"; + private const string LoopbackAddress = "127.0.0.1"; + + private readonly string _certificateDirectory; + private readonly X509Certificate2 _certificate; + private bool _trustedRootInstalled; + private bool _disposed; + + private IntegrationTestCertificate(string certificateDirectory, X509Certificate2 certificate) + { + _certificateDirectory = certificateDirectory; + _certificate = certificate; + } + + public string CertificateDirectory => _certificateDirectory; + + public string ContainerCertificateDirectory => ContainerCertificateDirectoryPath; + + public string Password => PasswordValue; + + public string RootCertificatePath => Path.Combine(_certificateDirectory, RootCertificateFileName); + + public string ServerCertificatePath => Path.Combine(_certificateDirectory, ServerCertificateFileName); + + public string Thumbprint => _certificate.Thumbprint ?? string.Empty; + + public static IntegrationTestCertificate Create() + { + string certificateDirectory = Path.Combine(Path.GetTempPath(), $"securityservice-cert-{Guid.NewGuid():N}"); + Directory.CreateDirectory(certificateDirectory); + + using RSA rsa = RSA.Create(2048); + CertificateRequest request = new CertificateRequest(RootSubjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true)); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true)); + + OidCollection enhancedKeyUsages = new OidCollection(); + enhancedKeyUsages.Add(new Oid("1.3.6.1.5.5.7.3.1")); + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsages, false)); + + SubjectAlternativeNameBuilder subjectAlternativeNames = new SubjectAlternativeNameBuilder(); + subjectAlternativeNames.AddDnsName(LocalhostName); + subjectAlternativeNames.AddIpAddress(IPAddress.Parse(LoopbackAddress)); + request.CertificateExtensions.Add(subjectAlternativeNames.Build()); + + request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); + + using X509Certificate2 certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30)); + byte[] pfxBytes = certificate.Export(X509ContentType.Pfx, PasswordValue); + byte[] certificateBytes = certificate.Export(X509ContentType.Cert); + + string serverCertificatePath = Path.Combine(certificateDirectory, ServerCertificateFileName); + string rootCertificatePath = Path.Combine(certificateDirectory, RootCertificateFileName); + File.WriteAllBytes(serverCertificatePath, pfxBytes); + File.WriteAllBytes(rootCertificatePath, certificateBytes); + + var result = new IntegrationTestCertificate(certificateDirectory, X509CertificateLoader.LoadPkcs12(pfxBytes, PasswordValue, X509KeyStorageFlags.EphemeralKeySet)); + result.InstallTrustedRoot(); + return result; + } + + public void InstallTrustedRoot() + { + ThrowIfDisposed(); + + using X509Store store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadWrite); + store.Add(_certificate); + _trustedRootInstalled = true; + } + + public string GetContainerCertificatePath() + { + return $"{ContainerCertificateDirectoryPath}/{ServerCertificateFileName}"; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + if (_trustedRootInstalled) + { + RemoveTrustedRoot(); + } + + if (Directory.Exists(_certificateDirectory)) + { + Directory.Delete(_certificateDirectory, recursive: true); + } + + _certificate.Dispose(); + _disposed = true; + } + + private void RemoveTrustedRoot() + { + using X509Store store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadWrite); + + foreach (X509Certificate2 certificate in store.Certificates.Find(X509FindType.FindByThumbprint, Thumbprint, validOnly: false)) + { + store.Remove(certificate); + } + + _trustedRootInstalled = false; + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_disposed, this); + } +} diff --git a/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs b/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs index 57497508..774b8e45 100644 --- a/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs +++ b/SecurityService.IntegrationTesting.Helpers/SecurityServiceSteps.cs @@ -242,11 +242,11 @@ public async Task WhenIGetTheIdentityResourcesIdentityResourceDetailsAreReturned List<(String, String)> roleList = new List<(String, String)>(); foreach (CreateRoleRequest request in requests){ Result? result = await this.SecurityServiceClient.CreateRole(request, cancellationToken).ConfigureAwait(false); - result.IsSuccess.ShouldBeTrue(); + result.IsSuccess.ShouldBeTrue(result.Message); } Result>? roles = await this.SecurityServiceClient.GetRoles(cancellationToken); - roles.IsSuccess.ShouldBeTrue(); + roles.IsSuccess.ShouldBeTrue(roles.Message); foreach (CreateRoleRequest request in requests) { RoleResponse r = roles.Data.Single(r => r.RoleName == request.RoleName); diff --git a/SecurityService.IntegrationTests/Common/DockerHelper.cs b/SecurityService.IntegrationTests/Common/DockerHelper.cs index 37e801f1..0fe6b94d 100644 --- a/SecurityService.IntegrationTests/Common/DockerHelper.cs +++ b/SecurityService.IntegrationTests/Common/DockerHelper.cs @@ -1,32 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Text; +#nullable enable +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; using Shared.Serialisation; namespace SecurityService.IntergrationTests.Common { using Client; - using Ductus.FluentDocker; - using Ductus.FluentDocker.Builders; - using Ductus.FluentDocker.Commands; - using Ductus.FluentDocker.Common; - using Ductus.FluentDocker.Model.Builders; - using Ductus.FluentDocker.Services; - using Ductus.FluentDocker.Services.Extensions; - using Microsoft.EntityFrameworkCore; + using SecurityService.IntegrationTesting.Helpers; using Shared.IntegrationTesting; - using System.Data.SqlClient; - using System.Linq; + using System; + using System.Collections.Generic; using System.Net; using System.Net.Http; - using System.Net.Security; using System.Security.Cryptography.X509Certificates; - using System.Threading; using System.Threading.Tasks; - + public class DockerHelper : Shared.IntegrationTesting.TestContainers.DockerHelper { - public ISecurityServiceClient SecurityServiceClient; + public ISecurityServiceClient? SecurityServiceClient; + private IntegrationTestCertificate? _integrationTestCertificate; String Serialise(Object arg) { @@ -38,71 +30,111 @@ Object Deserialise(String arg, Type type) return StringSerialiser.DeserializeObject(arg, type, new SerialiserOptions(SerialiserPropertyFormat.SnakeCase)); } - public async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices) + public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices) { - await base.StartContainersForScenarioRun(scenarioName, dockerServices); - - Func securityServiceBaseAddressResolver = api => $"https://localhost:{this.SecurityServicePort}"; - HttpClient httpClient = new HttpClient(); - this.SecurityServiceClient = new SecurityServiceClient(securityServiceBaseAddressResolver,httpClient, Serialise, Deserialise); + this._integrationTestCertificate = IntegrationTestCertificate.Create(); + + try + { + await base.StartContainersForScenarioRun(scenarioName, dockerServices); + + Func securityServiceBaseAddressResolver = _ => $"https://localhost:{this.SecurityServicePort}"; + HttpClient httpClient = this.CreatePinnedHttpClient(); + this.SecurityServiceClient = new SecurityServiceClient(securityServiceBaseAddressResolver, httpClient, Serialise, Deserialise); - ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault; + ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault; + } + catch + { + this.DisposeIntegrationTestCertificate(); + throw; + } } - /*public override DotNet.Testcontainers.Builders.ContainerBuilder SetupSecurityServiceContainer() + public override ContainerBuilder SetupSecurityServiceContainer() { - this.Trace("About to Start Security Container"); - List environmentVariables = this.GetCommonEnvironmentVariables(); - environmentVariables.Add($"ServiceOptions:PublicOrigin=https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}"); - environmentVariables.Add($"ServiceOptions:IssuerUrl=https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}"); - environmentVariables.Add("ASPNETCORE_ENVIRONMENT=IntegrationTest"); - environmentVariables.Add($"urls=https://*:{DockerPorts.SecurityServiceDockerPort}"); - - environmentVariables.Add("ServiceOptions:PasswordOptions:RequiredLength=6"); - environmentVariables.Add("ServiceOptions:PasswordOptions:RequireDigit=false"); - environmentVariables.Add("ServiceOptions:PasswordOptions:RequireUpperCase=false"); - environmentVariables.Add("ServiceOptions:UserOptions:RequireUniqueEmail=false"); - environmentVariables.Add("ServiceOptions:SignInOptions:RequireConfirmedEmail=false"); - - environmentVariables.Add(this.SetConnectionString("ConnectionStrings:PersistedGrantDbContext", $"PersistedGrantStore-{this.TestId}", this.UseSecureSqlServerDatabase)); - environmentVariables.Add(this.SetConnectionString("ConnectionStrings:ConfigurationDbContext", $"Configuration-{this.TestId}", this.UseSecureSqlServerDatabase)); - environmentVariables.Add(this.SetConnectionString("ConnectionStrings:AuthenticationDbContext", $"Authentication-{this.TestId}", this.UseSecureSqlServerDatabase)); + if (this._integrationTestCertificate is null) + { + throw new InvalidOperationException("Integration test certificate was not initialised before container setup."); + } - List additionalEnvironmentVariables = this.GetAdditionalVariables(ContainerType.SecurityService); + Dictionary environmentVariables = this.GetCommonEnvironmentVariables(); + environmentVariables.Add("ServiceOptions:PublicOrigin", $"https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}"); + environmentVariables.Add("ServiceOptions:IssuerUrl", $"https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}"); + environmentVariables.Add("ASPNETCORE_ENVIRONMENT", "IntegrationTest"); + environmentVariables.Add("urls", $"https://*:{DockerPorts.SecurityServiceDockerPort}"); + environmentVariables.Add("ServiceOptions:KestrelOptions:Path", this._integrationTestCertificate.GetContainerCertificatePath()); + environmentVariables.Add("ServiceOptions:KestrelOptions:Password", this._integrationTestCertificate.Password); + environmentVariables.Add("ServiceOptions:PasswordOptions:RequiredLength", "6"); + environmentVariables.Add("ServiceOptions:PasswordOptions:RequireDigit", "false"); + environmentVariables.Add("ServiceOptions:PasswordOptions:RequireUpperCase", "false"); + environmentVariables.Add("ServiceOptions:UserOptions:RequireUniqueEmail", "false"); + environmentVariables.Add("ServiceOptions:SignInOptions:RequireConfirmedEmail", "false"); + environmentVariables.Add("ConnectionStrings:PersistedGrantDbContext", this.SetConnectionString($"PersistedGrantStore-{this.TestId}", this.UseSecureSqlServerDatabase)); + environmentVariables.Add("ConnectionStrings:ConfigurationDbContext", this.SetConnectionString($"Configuration-{this.TestId}", this.UseSecureSqlServerDatabase)); + environmentVariables.Add("ConnectionStrings:AuthenticationDbContext", this.SetConnectionString($"Authentication-{this.TestId}", this.UseSecureSqlServerDatabase)); + Dictionary additionalEnvironmentVariables = this.GetAdditionalVariables(ContainerType.SecurityService); if (additionalEnvironmentVariables != null) { - environmentVariables.AddRange(additionalEnvironmentVariables); + foreach (KeyValuePair additionalEnvironmentVariable in additionalEnvironmentVariables) + { + environmentVariables.Add(additionalEnvironmentVariable.Key, additionalEnvironmentVariable.Value); + } } SimpleResults.Result<(String imageName, Boolean useLatest)> imageDetailsResult = this.GetImageDetails(ContainerType.SecurityService); if (imageDetailsResult.IsFailed) + { throw new Exception($"Image details not found for {ContainerType.SecurityService}"); + } - ContainerBuilder securityServiceContainer = new Builder().UseContainer().WithName(this.SecurityServiceContainerName) - .WithEnvironment(environmentVariables.ToArray()) - .UseImageDetails(imageDetailsResult.Data) - .MountHostFolder(this.DockerPlatform, this.HostTraceFolder) - .SetDockerCredentials(this.DockerCredentials); + ContainerBuilder securityServiceContainer = new ContainerBuilder(imageDetailsResult.Data.imageName) + .WithName(this.SecurityServiceContainerName) + .WithEnvironment(environmentVariables) + .WithBindMount(this._integrationTestCertificate.CertificateDirectory, this._integrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly); Int32? hostPort = this.GetHostPort(ContainerType.SecurityService); - if (hostPort == null) + if (hostPort is null || hostPort <= 0) { - securityServiceContainer = securityServiceContainer.ExposePort(DockerPorts.SecurityServiceDockerPort); + securityServiceContainer = securityServiceContainer.WithPortBinding(DockerPorts.SecurityServiceDockerPort, true); } else { - securityServiceContainer = securityServiceContainer.ExposePort(hostPort.Value, DockerPorts.SecurityServiceDockerPort); + securityServiceContainer = securityServiceContainer.WithPortBinding(DockerPorts.SecurityServiceDockerPort, hostPort.Value); } - // Now build and return the container return securityServiceContainer; - }*/ - - public override async Task CreateSubscriptions(){ - // No subscriptions needed + } + + public override Task CreateSubscriptions() + { + return Task.CompletedTask; + } + + public void DisposeIntegrationTestCertificate() + { + this._integrationTestCertificate?.Dispose(); + this._integrationTestCertificate = null; + } + + private HttpClient CreatePinnedHttpClient() + { + if (this._integrationTestCertificate is null) + { + throw new InvalidOperationException("Integration test certificate was not initialised before HttpClient creation."); + } + + HttpClientHandler handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (_, certificate, _, _) => + certificate is X509Certificate2 certificate2 && + String.Equals(certificate2.Thumbprint, this._integrationTestCertificate.Thumbprint, StringComparison.OrdinalIgnoreCase) + }; + + return new HttpClient(handler, disposeHandler: true); } } } diff --git a/SecurityService.IntegrationTests/Common/GenericSteps.cs b/SecurityService.IntegrationTests/Common/GenericSteps.cs index cabf07d0..4a144ba8 100644 --- a/SecurityService.IntegrationTests/Common/GenericSteps.cs +++ b/SecurityService.IntegrationTests/Common/GenericSteps.cs @@ -66,7 +66,14 @@ public async Task StartSystem() public async Task StopSystem(){ DockerServices sharedDockerServices = DockerServices.SqlServer; this.TestingContext.Logger.LogInformation("About to Stop Containers for Scenario Run"); - await this.TestingContext.DockerHelper.StopContainersForScenarioRun(sharedDockerServices).ConfigureAwait(false); + try + { + await this.TestingContext.DockerHelper.StopContainersForScenarioRun(sharedDockerServices).ConfigureAwait(false); + } + finally + { + this.TestingContext.DockerHelper.DisposeIntegrationTestCertificate(); + } this.TestingContext.Logger.LogInformation("Containers for Scenario Run Stopped"); } } diff --git a/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs b/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs index ebd68344..a9557036 100644 --- a/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs +++ b/SecurityService.OpenIdConnect.IntegrationTests/Common/DockerHelper.cs @@ -9,6 +9,7 @@ namespace SecurityService.IntergrationTests.Common { using Client; + using SecurityService.IntegrationTesting.Helpers; using Shared.HealthChecks; using Shared.IntegrationTesting; using Shared.Logger; @@ -21,6 +22,7 @@ namespace SecurityService.IntergrationTests.Common using System.Linq; using System.Net.Http; using System.Runtime.CompilerServices; + using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -36,6 +38,8 @@ public class DockerHelper : Shared.IntegrationTesting.TestContainers.DockerHelpe /// The security service client /// public ISecurityServiceClient SecurityServiceClient; + public HttpClient httpClient = new HttpClient(); + private IntegrationTestCertificate? _integrationTestCertificate; /// /// The security service test UI container name @@ -65,7 +69,6 @@ public DockerHelper() : base() #region Methods public Func securityServiceBaseAddressResolver; - public HttpClient httpClient = new HttpClient(); public override void SetupContainerNames() { @@ -97,17 +100,27 @@ Object Deserialise(String arg, Type type) public override async Task StartContainersForScenarioRun(String scenarioName, DockerServices dockerServices) { this.Trace($"Test Id is {this.TestId} and Scenario {scenarioName}"); + this._integrationTestCertificate = IntegrationTestCertificate.Create(); - await base.StartContainersForScenarioRun(scenarioName,dockerServices); + try + { + await base.StartContainersForScenarioRun(scenarioName,dockerServices); - await StartContainer(SetupSecurityServiceTestUIContainer, this.TestNetworks); + await StartContainer(SetupSecurityServiceTestUIContainer, this.TestNetworks); - securityServiceBaseAddressResolver = api => $"https://localhost:{this.SecurityServicePort}"; + securityServiceBaseAddressResolver = api => $"https://localhost:{this.SecurityServicePort}"; - this.SecurityServiceClient = new SecurityServiceClient(securityServiceBaseAddressResolver, httpClient, Serialise, Deserialise); + this.httpClient = this.CreatePinnedHttpClient(); + this.SecurityServiceClient = new SecurityServiceClient(securityServiceBaseAddressResolver, this.httpClient, Serialise, Deserialise); - DockerHelper.AddEntryToHostsFile("127.0.0.1", SecurityServiceContainerName); - DockerHelper.AddEntryToHostsFile("localhost", SecurityServiceContainerName); + DockerHelper.AddEntryToHostsFile("127.0.0.1", SecurityServiceContainerName); + DockerHelper.AddEntryToHostsFile("localhost", SecurityServiceContainerName); + } + catch + { + this.DisposeIntegrationTestCertificate(); + throw; + } } protected async Task DoTestUIHealthCheck() @@ -221,8 +234,10 @@ private ContainerBuilder SetupSecurityServiceTestUIContainer(){ - ContainerBuilder securityServiceTestUIContainer = new ContainerBuilder().WithName(this.SecurityServiceTestUIContainerName) - .WithEnvironment(environmentVariables).WithImage("securityservicetestui").WithPortBinding(5004, true); + ContainerBuilder securityServiceTestUIContainer = new ContainerBuilder("securityservicetestui") + .WithName(this.SecurityServiceTestUIContainerName) + .WithEnvironment(environmentVariables) + .WithPortBinding(5004, true); return securityServiceTestUIContainer; } @@ -231,11 +246,18 @@ public override ContainerBuilder SetupSecurityServiceContainer() { this.Trace("About to Start Security Container"); + if (this._integrationTestCertificate is null) + { + throw new InvalidOperationException("Integration test certificate was not initialised before container setup."); + } + var environmentVariables = this.GetCommonEnvironmentVariables(); environmentVariables.Add($"ServiceOptions:PublicOrigin",$"https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}"); environmentVariables.Add($"ServiceOptions:IssuerUrl",$"https://{this.SecurityServiceContainerName}:{DockerPorts.SecurityServiceDockerPort}"); environmentVariables.Add("ASPNETCORE_ENVIRONMENT","IntegrationTest"); environmentVariables.Add($"urls",$"https://*:{DockerPorts.SecurityServiceDockerPort}"); + environmentVariables.Add("ServiceOptions:KestrelOptions:Path", this._integrationTestCertificate.GetContainerCertificatePath()); + environmentVariables.Add("ServiceOptions:KestrelOptions:Password", this._integrationTestCertificate.Password); environmentVariables.Add($"ServiceOptions:PasswordOptions:RequiredLength","6"); environmentVariables.Add($"ServiceOptions:PasswordOptions:RequireDigit","false"); @@ -260,11 +282,39 @@ public override ContainerBuilder SetupSecurityServiceContainer() if (imageDetailsResult.IsFailed) throw new Exception($"Image details not found for {ContainerType.SecurityService}"); - ContainerBuilder securityServiceContainer = new ContainerBuilder().WithName(this.SecurityServiceContainerName).WithEnvironment(environmentVariables).WithImage(imageDetailsResult.Data.imageName).WithPortBinding(DockerPorts.SecurityServiceDockerPort, DockerPorts.SecurityServiceDockerPort).MountHostFolder(this.DockerPlatform, this.HostTraceFolder); + ContainerBuilder securityServiceContainer = new ContainerBuilder(imageDetailsResult.Data.imageName) + .WithName(this.SecurityServiceContainerName) + .WithEnvironment(environmentVariables) + .WithPortBinding(DockerPorts.SecurityServiceDockerPort, DockerPorts.SecurityServiceDockerPort) + .WithBindMount(this._integrationTestCertificate.CertificateDirectory, this._integrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly) + .MountHostFolder(this.DockerPlatform, this.HostTraceFolder); return securityServiceContainer; } + public void DisposeIntegrationTestCertificate() + { + this._integrationTestCertificate?.Dispose(); + this._integrationTestCertificate = null; + } + + private HttpClient CreatePinnedHttpClient() + { + if (this._integrationTestCertificate is null) + { + throw new InvalidOperationException("Integration test certificate was not initialised before HttpClient creation."); + } + + HttpClientHandler handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (_, certificate, _, _) => + certificate is X509Certificate2 certificate2 && + String.Equals(certificate2.Thumbprint, this._integrationTestCertificate.Thumbprint, StringComparison.OrdinalIgnoreCase) + }; + + return new HttpClient(handler, disposeHandler: true); + } + #endregion } -} \ No newline at end of file +} diff --git a/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs b/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs index 472bb5cd..5d779730 100644 --- a/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs +++ b/SecurityService.OpenIdConnect.IntegrationTests/Common/GenericSteps.cs @@ -60,7 +60,14 @@ public async Task StartSystem() public async Task StopSystem(){ DockerServices sharedDockerServices = DockerServices.None; this.TestingContext.DockerHelper.Logger.LogInformation("About to Stop Containers for Scenario Run"); - await this.TestingContext.DockerHelper.StopContainersForScenarioRun(sharedDockerServices).ConfigureAwait(false); + try + { + await this.TestingContext.DockerHelper.StopContainersForScenarioRun(sharedDockerServices).ConfigureAwait(false); + } + finally + { + this.TestingContext.DockerHelper.DisposeIntegrationTestCertificate(); + } this.TestingContext.DockerHelper.Logger.LogInformation("Containers for Scenario Run Stopped"); } }