too much for one commit

This commit is contained in:
Nils Blomgren
2026-05-13 14:01:32 +02:00
parent a96dd33039
commit 328d494530
394 changed files with 98104 additions and 78 deletions

View File

@@ -0,0 +1,609 @@
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;
/// <summary>
/// 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.
/// </summary>
public class TrustBundleAndInternalCATests
{
private readonly InMemoryClusterRepository repository;
private readonly Mock<IComponentInstaller> trustManagerInstaller;
private readonly Mock<IComponentInstaller> internalCaInstaller;
public TrustBundleAndInternalCATests()
{
repository = new InMemoryClusterRepository();
trustManagerInstaller = new Mock<IComponentInstaller>();
trustManagerInstaller.Setup(i => i.ComponentName).Returns("trust-manager");
internalCaInstaller = new Mock<IComponentInstaller>();
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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "trust-manager",
Message: "trust-manager 0.14.0 installed with platform-trust-bundle Bundle",
Actions: new List<string>
{
"Ensured namespace 'cert-manager'",
"Helm install trust-manager v0.14.0",
"Created Bundle 'platform-trust-bundle'"
}));
DeployComponentHandler handler = new(repository, new List<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("trust-manager", Version: "0.14.0",
Parameters: new Dictionary<string, string>
{
["bundleName"] = "platform-trust-bundle",
["targetNamespaces"] = "*"
});
// Act
Result<InstallResult> 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<ComponentInstallOptions>(o =>
o.Version == "0.14.0" &&
o.Parameters!["bundleName"] == "platform-trust-bundle"),
It.IsAny<CancellationToken>()), 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<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "trust-manager",
Message: "Trust bundle updated with 2 additional certificates",
Actions: new List<string>
{
"Added certificate 'corporate-root-ca' to bundle",
"Added certificate 'partner-api-ca' to bundle"
}));
List<IComponentInstaller> 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<string, string>
{
["additionalCertificates"] = "corporate-root-ca:LS0tLS1CRUdJTi...;partner-api-ca:LS0tLS1CRUdJTi...",
["bundleName"] = "platform-trust-bundle"
});
Result<InstallResult> 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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "internal-ca",
Message: "Internal CA provisioned and added to trust bundle",
Actions: new List<string>
{
"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<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("internal-ca",
Parameters: new Dictionary<string, string>
{
["caName"] = "platform-internal-ca",
["bundleName"] = "platform-trust-bundle",
["caOrganization"] = "EntKube Platform",
["caDurationDays"] = "3650"
});
// Act
Result<InstallResult> 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<ComponentInstallOptions>(o =>
o.Parameters!["caName"] == "platform-internal-ca" &&
o.Parameters["bundleName"] == "platform-trust-bundle"),
It.IsAny<CancellationToken>()), 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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "internal-ca",
Message: "Existing internal CA adopted and added to trust bundle",
Actions: new List<string>
{
"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<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("internal-ca",
Parameters: new Dictionary<string, string>
{
["adoptExisting"] = "true",
["existingIssuerName"] = "existing-ca",
["bundleName"] = "platform-trust-bundle"
});
// Act
Result<InstallResult> 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<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "internal-ca",
Message: "Internal CA reconfigured",
Actions: new List<string>
{
"Updated CA Certificate duration to 7300 days",
"Refreshed root CA in Bundle 'platform-trust-bundle'"
}));
DeployComponentHandler handler = new(repository, new List<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
// Act
ComponentConfiguration config = new("cert-manager", new Dictionary<string, string>
{
["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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "internal-ca",
Message: "Internal CA reconfigured",
Actions: new List<string>
{
"Updated CA Certificate duration to 7300 days",
"Refreshed root CA in Bundle 'platform-trust-bundle'"
}));
DeployComponentRequest request = new("internal-ca",
Parameters: config.Values);
Result<InstallResult> 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<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: false,
ComponentName: "internal-ca",
Message: "cert-manager is required but not installed",
Actions: new List<string> { "Prerequisite check failed: cert-manager not found" }));
DeployComponentHandler handler = new(repository, new List<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("internal-ca",
Parameters: new Dictionary<string, string>
{
["caName"] = "platform-internal-ca",
["bundleName"] = "platform-trust-bundle"
});
// Act
Result<InstallResult> 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<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "trust-manager",
Message: "Certificate added to trust bundle",
Actions: new List<string>
{
"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<IComponentInstaller> installers = new() { trustManagerInstaller.Object, internalCaInstaller.Object };
ConfigureComponentHandler configHandler = new(repository, installers);
ConfigureComponentRequest configRequest = new(
ComponentName: "trust-manager",
Values: new Dictionary<string, string>
{
["addCertificate"] = "true",
["certificateName"] = "partner-api",
["certificateData"] = "LS0tLS1CRUdJTi...",
["bundleName"] = "platform-trust-bundle"
});
// Act
Result<InstallResult> 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<IComponentInstaller> domainCaInstaller = new();
domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca");
domainCaInstaller
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "domain-ca",
Message: "Domain CA 'corp-internal-ca' provisioned for *.internal.corp.com",
Actions: new List<string>
{
"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<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("domain-ca",
Parameters: new Dictionary<string, string>
{
["caName"] = "corp-internal-ca",
["domains"] = "*.internal.corp.com,*.svc.corp.local",
["caOrganization"] = "Corp Internal",
["caDurationDays"] = "1825",
["bundleName"] = "platform-trust-bundle"
});
// Act
Result<InstallResult> 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<ComponentInstallOptions>(o =>
o.Parameters!["caName"] == "corp-internal-ca" &&
o.Parameters["domains"] == "*.internal.corp.com,*.svc.corp.local"),
It.IsAny<CancellationToken>()), 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<IComponentInstaller> domainCaInstaller = new();
domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca");
domainCaInstaller
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "domain-ca",
Message: "External CA 'digicert-example-com' imported for *.example.com",
Actions: new List<string>
{
"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<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("domain-ca",
Parameters: new Dictionary<string, string>
{
["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<InstallResult> 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<IComponentInstaller> domainCaInstaller = new();
domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca");
domainCaInstaller
.Setup(i => i.InstallAsync(cluster, It.IsAny<ComponentInstallOptions>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "domain-ca",
Message: "External CA certificate 'partner-root-ca' added to trust bundle",
Actions: new List<string>
{
"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<IComponentInstaller>
{
trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object
}, Microsoft.Extensions.Logging.Abstractions.NullLogger<DeployComponentHandler>.Instance);
DeployComponentRequest request = new("domain-ca",
Parameters: new Dictionary<string, string>
{
["caName"] = "partner-root-ca",
["importExternal"] = "true",
["tlsCert"] = "LS0tLS1CRUdJTi...", // cert only, no key
["bundleName"] = "platform-trust-bundle"
});
// Act
Result<InstallResult> 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<IComponentInstaller> domainCaInstaller = new();
domainCaInstaller.Setup(i => i.ComponentName).Returns("domain-ca");
domainCaInstaller
.Setup(i => i.ConfigureAsync(cluster, It.IsAny<ComponentConfiguration>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new InstallResult(
Success: true,
ComponentName: "domain-ca",
Message: "Domain CA 'corp-internal-ca' reconfigured",
Actions: new List<string>
{
"Updated Certificate policy: [*.internal.corp.com, *.new-domain.corp.com]"
}));
List<IComponentInstaller> installers = new()
{
trustManagerInstaller.Object, internalCaInstaller.Object, domainCaInstaller.Object
};
ConfigureComponentHandler configHandler = new(repository, installers);
ConfigureComponentRequest configRequest = new(
ComponentName: "domain-ca",
Values: new Dictionary<string, string>
{
["caName"] = "corp-internal-ca",
["domains"] = "*.internal.corp.com,*.new-domain.corp.com"
});
// Act
Result<InstallResult> 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;
}
}