Skip to content
Merged
Show file tree
Hide file tree
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
133 changes: 133 additions & 0 deletions Shared.IntegrationTesting/IntegrationTestCertificate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Shared.IntegrationTesting
{
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);
}
}
}
78 changes: 52 additions & 26 deletions Shared.IntegrationTesting/TestContainers/BaseDockerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -517,47 +517,73 @@ public virtual ContainerBuilder SetupMessagingServiceContainer(){
return messagingServiceContainer;
}

public virtual ContainerBuilder SetupSecurityServiceContainer(){
this.Trace("About to Start Security Container");
protected IntegrationTestCertificate? IntegrationTestCertificate;

Dictionary<String, String> 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}");
public virtual ContainerBuilder SetupSecurityServiceContainer()
{
this.Trace("About to Start Security Container");

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");
if (this.IntegrationTestCertificate is null)
{
throw new InvalidOperationException("Integration test certificate was not initialised before container setup.");
}

environmentVariables.Add("ConnectionStrings:PersistedGrantDbContext", this.SetConnectionString( $"PersistedGrantStore-{this.TestId}", this.UseSecureSqlServerDatabase));
Dictionary<String, String> 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("ServiceOptions:ManagementBootstrap:Enabled", "true");
environmentVariables.Add("ServiceOptions:ManagementBootstrap:ClientId", "management-bootstrap");
environmentVariables.Add("ServiceOptions:ManagementBootstrap:ClientSecret", "management-bootstrap-secret");
environmentVariables.Add("ServiceOptions:OAuth:LegacyGrantTypeClients:password:0", "merchantClient");
environmentVariables.Add("ServiceOptions:OAuth:LegacyGrantTypeClients:hybrid:0", "testclient2");
environmentVariables.Add("ServiceOptions:OAuth:EnableLegacyGrantTypes", "true");
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<String, String> additionalEnvironmentVariables = this.GetAdditionalVariables(ContainerType.SecurityService);

foreach (KeyValuePair<String, String> additionalEnvironmentVariable in additionalEnvironmentVariables) {
environmentVariables.Add(additionalEnvironmentVariable.Key, additionalEnvironmentVariable.Value);
if (additionalEnvironmentVariables != null)
{
foreach (KeyValuePair<String, String> additionalEnvironmentVariable in additionalEnvironmentVariables)
{
environmentVariables.Add(additionalEnvironmentVariable.Key, additionalEnvironmentVariable.Value);
}
}

(String imageName, Boolean useLatest) imageDetails = this.GetImageDetails(ContainerType.SecurityService).Data;
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 ContainerBuilder()
.WithName(this.SecurityServiceContainerName) // similar to WithName()
.WithImage(imageDetails.imageName)
ContainerBuilder securityServiceContainer = new ContainerBuilder(imageDetailsResult.Data.imageName)
.WithName(this.SecurityServiceContainerName)
.WithEnvironment(environmentVariables)
.MountHostFolder(this.DockerPlatform, this.HostTraceFolder)
.WithPortBinding(DockerPorts.SecurityServiceDockerPort, true);

// TODO: might need this but not sure yet
//Int32? hostPort = this.GetHostPort(ContainerType.SecurityService);
//securityServiceContainer = hostPort == null ? securityServiceContainer.ExposePort(DockerPorts.SecurityServiceDockerPort) : securityServiceContainer.ExposePort(hostPort.Value, DockerPorts.SecurityServiceDockerPort);
.WithBindMount(this.IntegrationTestCertificate.CertificateDirectory, this.IntegrationTestCertificate.ContainerCertificateDirectory, AccessMode.ReadOnly);

Int32? hostPort = this.GetHostPort(ContainerType.SecurityService);
if (hostPort is null || hostPort <= 0)
{
securityServiceContainer = securityServiceContainer.WithPortBinding(DockerPorts.SecurityServiceDockerPort, true);
}
else
{
securityServiceContainer = securityServiceContainer.WithPortBinding(DockerPorts.SecurityServiceDockerPort, hostPort.Value);
}

return securityServiceContainer;
}


public virtual ContainerBuilder ConfigureSqlContainer()
{
this.Trace("About to start SQL Server Container");
Expand Down
Loading
Loading