| File: TestServices\DcpReadyTestHost.cs | Web Access |
| Project: src\tests\Aspire.Hosting.Native.Core.Tests\Aspire.Hosting.Native.Core.Tests.csproj (Aspire.Hosting.Native.Core.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting.Native.Core.Tests.TestServices; /// <summary>Serves authenticated readiness with a real local certificate chain and kubeconfig.</summary> internal sealed class DcpReadyTestHost( WebApplication application, DirectoryInfo directory, X509Certificate2 authority, X509Certificate2 certificate) : IAsyncDisposable { public string KubeconfigPath => Path.Combine(directory.FullName, "kubeconfig"); public static async Task<DcpReadyTestHost> StartAsync(CancellationToken cancellationToken) { var root = new DirectoryInfo(AppContext.BaseDirectory); while (!File.Exists(Path.Combine(root.FullName, "Aspire.slnx"))) { root = root.Parent ?? throw new InvalidOperationException("Repository root not found."); } var directory = Directory.CreateDirectory(Path.Combine(root.FullName, "artifacts", "dcp-ready", Guid.NewGuid().ToString("N"))); var now = TimeProvider.System.GetUtcNow(); using var authorityKey = RSA.Create(2048); var authorityRequest = new CertificateRequest("CN=Native DCP readiness test authority", authorityKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); authorityRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); authorityRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign, true)); var authority = authorityRequest.CreateSelfSigned(now.AddMinutes(-1), now.AddDays(1)); using var serverKey = RSA.Create(2048); var serverRequest = new CertificateRequest("CN=localhost", serverKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); serverRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true)); serverRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true)); serverRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension( new OidCollection { new("1.3.6.1.5.5.7.3.1") }, true)); var names = new SubjectAlternativeNameBuilder(); names.AddIpAddress(IPAddress.Loopback); serverRequest.CertificateExtensions.Add(names.Build()); using var signed = serverRequest.Create(authority, now.AddMinutes(-1), now.AddHours(1), RandomNumberGenerator.GetBytes(16)); using var keyedCertificate = signed.CopyWithPrivateKey(serverKey); // Schannel needs an imported private key rather than the ephemeral key // attached by CopyWithPrivateKey. Default key storage is cleaned up on disposal. var certificate = X509CertificateLoader.LoadPkcs12(keyedCertificate.Export(X509ContentType.Pfx), password: null); var builder = WebApplication.CreateSlimBuilder(new WebApplicationOptions { Args = [] }); builder.WebHost.ConfigureKestrel(options => options.Listen(IPAddress.Loopback, 0, listen => listen.UseHttps(certificate))); var application = builder.Build(); application.MapGet("/readyz", (HttpContext context) => { context.Response.StatusCode = context.Request.Headers.Authorization == "Bearer test-readiness-token" ? 200 : 401; return Task.CompletedTask; }); var host = new DcpReadyTestHost(application, directory, authority, certificate); try { await application.StartAsync(cancellationToken); var addresses = application.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()!; await File.WriteAllTextAsync(host.KubeconfigPath, $""" clusters: - cluster: server: {addresses.Addresses.Single()} certificate-authority-data: {Convert.ToBase64String(authority.RawData)} users: - user: token: test-readiness-token """, cancellationToken); return host; } catch { await host.DisposeAsync(); throw; } } public async ValueTask DisposeAsync() { await application.DisposeAsync(); certificate.Dispose(); authority.Dispose(); directory.Delete(recursive: true); } }