using EntKube.Clusters.Domain; using EntKube.Clusters.Features.AdoptCluster; using EntKube.Clusters.Features.AdoptCluster.ConfigureComponent; using EntKube.Clusters.Features.AdoptCluster.DeployComponent; using EntKube.Clusters.Infrastructure; using EntKube.SharedKernel.Domain; using FluentAssertions; using Moq; namespace EntKube.Clusters.Tests.Features; /// /// Tests for the trust bundle and internal CA components. These two are co-dependent: /// /// 1. **cert-manager** — prerequisite, already tested elsewhere /// 2. **trust-manager** — distributes CA bundles to namespaces via Bundle CRDs /// 3. **internal-ca** — provisions a self-signed CA ClusterIssuer via cert-manager, /// then adds its root certificate to the trust bundle /// /// The dependency chain: cert-manager → internal-ca → trust-manager (bundle update) /// Both internal-ca and trust-manager require cert-manager to be installed first. /// public class TrustBundleAndInternalCATests { private readonly InMemoryClusterRepository repository; private readonly Mock trustManagerInstaller; private readonly Mock internalCaInstaller; public TrustBundleAndInternalCATests() { repository = new InMemoryClusterRepository(); trustManagerInstaller = new Mock(); trustManagerInstaller.Setup(i => i.ComponentName).Returns("trust-manager"); internalCaInstaller = new Mock(); internalCaInstaller.Setup(i => i.ComponentName).Returns("internal-ca"); } // ─── Trust Manager Installer ────────────────────────────────────────────── [Fact] public async Task TrustManager_Install_DeploysHelmAndCreatesBundle() { // Arrange — cert-manager is already installed. Now we deploy trust-manager // to distribute CA bundles cluster-wide. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); trustManagerInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "trust-manager", Message: "trust-manager 0.14.0 installed with platform-trust-bundle Bundle", Actions: new List { "Ensured namespace 'cert-manager'", "Helm install trust-manager v0.14.0", "Created Bundle 'platform-trust-bundle'" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("trust-manager", Version: "0.14.0", Parameters: new Dictionary { ["bundleName"] = "platform-trust-bundle", ["targetNamespaces"] = "*" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — trust-manager installed and Bundle CR created. result.IsSuccess.Should().BeTrue(); result.Value!.ComponentName.Should().Be("trust-manager"); result.Value.Actions.Should().Contain(a => a.Contains("Bundle")); trustManagerInstaller.Verify(i => i.InstallAsync( cluster, It.Is(o => o.Version == "0.14.0" && o.Parameters!["bundleName"] == "platform-trust-bundle"), It.IsAny()), Times.Once); } [Fact] public async Task TrustManager_Configure_AddsCertificatesToBundle() { // Arrange — trust-manager is installed. We want to add extra CA certificates // to the platform trust bundle (e.g., corporate PKI, external services). KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); trustManagerInstaller .Setup(i => i.ConfigureAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "trust-manager", Message: "Trust bundle updated with 2 additional certificates", Actions: new List { "Added certificate 'corporate-root-ca' to bundle", "Added certificate 'partner-api-ca' to bundle" })); List installers = new() { trustManagerInstaller.Object, internalCaInstaller.Object }; ConfigureComponentHandler configHandler = new(repository, installers); // Act — The user adds managed certificates to the trust bundle. ConfigureComponentRequest configRequest = new( ComponentName: "trust-manager", Values: new Dictionary { ["additionalCertificates"] = "corporate-root-ca:LS0tLS1CRUdJTi...;partner-api-ca:LS0tLS1CRUdJTi...", ["bundleName"] = "platform-trust-bundle" }); Result result = await configHandler.HandleAsync(cluster.Id, configRequest); // Assert result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("corporate-root-ca")); } // ─── Internal CA ────────────────────────────────────────────────────────── [Fact] public async Task InternalCA_Install_CreatesSelfSignedCAAndAddsToBundle() { // Arrange — cert-manager and trust-manager are installed. Now we create // an internal CA that signs service certificates and whose root is // distributed via the trust bundle. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); internalCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "internal-ca", Message: "Internal CA provisioned and added to trust bundle", Actions: new List { "Created self-signed ClusterIssuer 'selfsigned-bootstrap'", "Created root CA Certificate 'platform-internal-ca'", "Created CA ClusterIssuer 'internal-ca'", "Added root CA to Bundle 'platform-trust-bundle'" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("internal-ca", Parameters: new Dictionary { ["caName"] = "platform-internal-ca", ["bundleName"] = "platform-trust-bundle", ["caOrganization"] = "EntKube Platform", ["caDurationDays"] = "3650" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — CA created and injected into trust bundle. result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("ClusterIssuer 'internal-ca'")); result.Value.Actions.Should().Contain(a => a.Contains("Bundle")); internalCaInstaller.Verify(i => i.InstallAsync( cluster, It.Is(o => o.Parameters!["caName"] == "platform-internal-ca" && o.Parameters["bundleName"] == "platform-trust-bundle"), It.IsAny()), Times.Once); } [Fact] public async Task InternalCA_Adopt_DiscoversExistingCAAndRegistersInBundle() { // Arrange — A cluster already has a CA ClusterIssuer. The adoption check // should discover it and register its root cert in the trust bundle. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); internalCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "internal-ca", Message: "Existing internal CA adopted and added to trust bundle", Actions: new List { "Discovered existing CA ClusterIssuer 'existing-ca'", "Read root CA certificate from Secret 'existing-ca-secret'", "Added root CA to Bundle 'platform-trust-bundle'" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("internal-ca", Parameters: new Dictionary { ["adoptExisting"] = "true", ["existingIssuerName"] = "existing-ca", ["bundleName"] = "platform-trust-bundle" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — Existing CA adopted and cert added to bundle. result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("Discovered existing CA")); result.Value.Actions.Should().Contain(a => a.Contains("Bundle")); } [Fact] public async Task InternalCA_Configure_RotatesCACertificate() { // Arrange — The internal CA already exists. We want to configure it // (e.g., update the duration or rotate the CA cert). KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); internalCaInstaller .Setup(i => i.ConfigureAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "internal-ca", Message: "Internal CA reconfigured", Actions: new List { "Updated CA Certificate duration to 7300 days", "Refreshed root CA in Bundle 'platform-trust-bundle'" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); // Act ComponentConfiguration config = new("cert-manager", new Dictionary { ["caDurationDays"] = "7300", ["bundleName"] = "platform-trust-bundle" }); // Use the configure path through the deploy handler // (the handler routes based on whether the component is already installed) internalCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "internal-ca", Message: "Internal CA reconfigured", Actions: new List { "Updated CA Certificate duration to 7300 days", "Refreshed root CA in Bundle 'platform-trust-bundle'" })); DeployComponentRequest request = new("internal-ca", Parameters: config.Values); Result result = await handler.HandleAsync(cluster.Id, request); // Assert result.IsSuccess.Should().BeTrue(); } // ─── Dependency Chain ───────────────────────────────────────────────────── [Fact] public async Task InternalCA_WithoutCertManager_ReportsFailure() { // Arrange — cert-manager is NOT installed. internal-ca cannot function. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); internalCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: false, ComponentName: "internal-ca", Message: "cert-manager is required but not installed", Actions: new List { "Prerequisite check failed: cert-manager not found" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("internal-ca", Parameters: new Dictionary { ["caName"] = "platform-internal-ca", ["bundleName"] = "platform-trust-bundle" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — DeployComponentHandler wraps a failed InstallResult in Result.Failure. result.IsFailure.Should().BeTrue(); result.Error.Should().Contain("cert-manager"); } [Fact] public async Task TrustManager_AddManagedCertificate_UpdatesBundleWithNewCert() { // Arrange — trust-manager is installed. We add a new managed certificate // (e.g., a partner's CA cert that our services need to trust). KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); trustManagerInstaller .Setup(i => i.ConfigureAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "trust-manager", Message: "Certificate added to trust bundle", Actions: new List { "Created ConfigMap 'managed-cert-partner-api' with certificate data", "Updated Bundle 'platform-trust-bundle' to include new source" })); // Use the configure handler path (component already installed) List installers = new() { trustManagerInstaller.Object, internalCaInstaller.Object }; ConfigureComponentHandler configHandler = new(repository, installers); ConfigureComponentRequest configRequest = new( ComponentName: "trust-manager", Values: new Dictionary { ["addCertificate"] = "true", ["certificateName"] = "partner-api", ["certificateData"] = "LS0tLS1CRUdJTi...", ["bundleName"] = "platform-trust-bundle" }); // Act Result result = await configHandler.HandleAsync(cluster.Id, configRequest); // Assert — Certificate stored and bundle updated to reference it. result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("ConfigMap")); result.Value.Actions.Should().Contain(a => a.Contains("Bundle")); } // ─── Domain-Scoped CA ─────────────────────────────────────────────────── [Fact] public async Task DomainCA_Install_CreatesSeparateCAForInternalDomains() { // Arrange — The platform already has an internal-ca for service mesh certs. // Now the team wants a SEPARATE CA specifically for *.internal.corp.com // so they can issue certs for internal services on that domain. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); Mock domainCaInstaller = new(); domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca"); domainCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "domain-ca", Message: "Domain CA 'corp-internal-ca' provisioned for *.internal.corp.com", Actions: new List { "Created root CA Certificate 'corp-internal-ca'", "Created CA ClusterIssuer 'corp-internal-ca'", "Created Certificate policy: only signs for [*.internal.corp.com, *.svc.corp.local]", "Added root CA to Bundle 'platform-trust-bundle'" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("domain-ca", Parameters: new Dictionary { ["caName"] = "corp-internal-ca", ["domains"] = "*.internal.corp.com,*.svc.corp.local", ["caOrganization"] = "Corp Internal", ["caDurationDays"] = "1825", ["bundleName"] = "platform-trust-bundle" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — Separate domain CA created with scope restrictions. result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("ClusterIssuer 'corp-internal-ca'")); result.Value.Actions.Should().Contain(a => a.Contains("policy")); result.Value.Actions.Should().Contain(a => a.Contains("Bundle")); domainCaInstaller.Verify(i => i.InstallAsync( cluster, It.Is(o => o.Parameters!["caName"] == "corp-internal-ca" && o.Parameters["domains"] == "*.internal.corp.com,*.svc.corp.local"), It.IsAny()), Times.Once); } [Fact] public async Task DomainCA_ImportExternalCA_CreatesIssuerFromProvidedCertAndKey() { // Arrange — The team has a public SSL cert from DigiCert for *.example.com // and wants to use it as a CA/issuer for subdomains, or simply wants // cert-manager to serve this cert for ingress. We import it. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); Mock domainCaInstaller = new(); domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca"); domainCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "domain-ca", Message: "External CA 'digicert-example-com' imported for *.example.com", Actions: new List { "Created TLS Secret 'digicert-example-com-tls' with imported cert+key", "Created CA ClusterIssuer 'digicert-example-com'", "Added CA certificate to Bundle 'platform-trust-bundle'" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("domain-ca", Parameters: new Dictionary { ["caName"] = "digicert-example-com", ["importExternal"] = "true", ["tlsCert"] = "LS0tLS1CRUdJTi...", // base64 PEM cert ["tlsKey"] = "LS0tLS1CRUdJTi...", // base64 PEM key ["domains"] = "*.example.com", ["bundleName"] = "platform-trust-bundle" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — External cert imported, ClusterIssuer created, trust bundle updated. result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("TLS Secret")); result.Value.Actions.Should().Contain(a => a.Contains("ClusterIssuer")); result.Value.Actions.Should().Contain(a => a.Contains("Bundle")); } [Fact] public async Task DomainCA_ImportExternalCertOnly_AddsToBundleWithoutIssuer() { // Arrange — The team has an external CA root cert (no private key) that // services need to trust (e.g., partner API CA). We just need it in the // trust bundle — no ClusterIssuer needed since we can't sign certs with it. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); Mock domainCaInstaller = new(); domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca"); domainCaInstaller .Setup(i => i.InstallAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "domain-ca", Message: "External CA certificate 'partner-root-ca' added to trust bundle", Actions: new List { "Created ConfigMap 'managed-cert-partner-root-ca' with CA certificate", "Added certificate to Bundle 'platform-trust-bundle'", "No ClusterIssuer created (no private key provided — trust-only)" })); DeployComponentHandler handler = new(repository, new List { trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object }, Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); DeployComponentRequest request = new("domain-ca", Parameters: new Dictionary { ["caName"] = "partner-root-ca", ["importExternal"] = "true", ["tlsCert"] = "LS0tLS1CRUdJTi...", // cert only, no key ["bundleName"] = "platform-trust-bundle" }); // Act Result result = await handler.HandleAsync(cluster.Id, request); // Assert — Cert added to trust bundle but no issuer (can't sign without key). result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("trust-only")); result.Value.Actions.Should().NotContain(a => a.Contains("ClusterIssuer 'partner-root-ca'") && !a.Contains("No ClusterIssuer")); } [Fact] public async Task DomainCA_Configure_UpdatesDomainRestrictions() { // Arrange — An existing domain CA needs its allowed domains updated. KubernetesCluster cluster = CreateConnectedCluster(); await repository.AddAsync(cluster); Mock domainCaInstaller = new(); domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca"); domainCaInstaller .Setup(i => i.ConfigureAsync(cluster, It.IsAny(), It.IsAny())) .ReturnsAsync(new InstallResult( Success: true, ComponentName: "domain-ca", Message: "Domain CA 'corp-internal-ca' reconfigured", Actions: new List { "Updated Certificate policy: [*.internal.corp.com, *.new-domain.corp.com]" })); List installers = new() { trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object }; ConfigureComponentHandler configHandler = new(repository, installers); ConfigureComponentRequest configRequest = new( ComponentName: "domain-ca", Values: new Dictionary { ["caName"] = "corp-internal-ca", ["domains"] = "*.internal.corp.com,*.new-domain.corp.com" }); // Act Result result = await configHandler.HandleAsync(cluster.Id, configRequest); // Assert result.IsSuccess.Should().BeTrue(); result.Value!.Actions.Should().Contain(a => a.Contains("new-domain")); } private static KubernetesCluster CreateConnectedCluster() { KubernetesCluster cluster = KubernetesCluster.Register( "prod-cluster", "https://k8s.example.com:6443", "apiVersion: v1\nkind: Config\nclusters: []\nusers: []\ncontexts: []", Guid.NewGuid(), "prod-ctx", Guid.NewGuid()); cluster.MarkConnected(); return cluster; } }