mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:21:37 +00:00
5567d4b411
Implement both M47 connectors with full cross-layer wiring: Kubernetes Secrets target: DNS-1123 validation, kubernetes.io/tls Secret create-or-update, chain concatenation, serial number validation, Helm RBAC gating. 18 tests. AWS ACM Private CA issuer: synchronous issuance (like Vault), ARN regex validation, RFC 5280 revocation reason mapping, CA cert retrieval, factory + env var seeding. 23 tests. Cross-cutting: domain types, service validation, config, factory, agent dispatch, frontend (TargetsPage, issuerTypes), OpenAPI, seed data, Helm chart, connectors docs, README. Testing docs (testing-guide, qa-test-guide, qa_test.go) with Parts thematically integrated near related connectors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
648 lines
17 KiB
Go
648 lines
17 KiB
Go
package k8ssecret
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/shankar0123/certctl/internal/connector/target"
|
|
)
|
|
|
|
// testLogger returns a slog.Logger for test output.
|
|
func testLogger() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelWarn}))
|
|
}
|
|
|
|
// --- Test Certificate Generation ---
|
|
|
|
// generateTestCert creates a simple self-signed certificate for testing.
|
|
// Returns cert PEM and key PEM strings.
|
|
func generateTestCert(t *testing.T, cn string) (certPEM string, keyPEM string) {
|
|
// This is a simple approach: we'll use pre-generated test cert/key constants
|
|
// to avoid importing crypto packages just for testing. Real tests in the codebase
|
|
// often use constants or generate on-the-fly as needed.
|
|
|
|
// For simplicity, use a fixed test certificate (self-signed)
|
|
certPEM = `-----BEGIN CERTIFICATE-----
|
|
MIICljCCAX4CCQDfhEj1uAEUBDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
|
|
UzAeFw0yMzAxMDExMjAwMDBaFw0yNDAxMDExMjAwMDBaMA0xCzAJBgNVBAYTAlVT
|
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1jlPyZjxN5pQvhW4LkL9
|
|
+QkXlQ3wF3mHdBwZNLFsGdEv9kXYGlQYLU6k5Z6Xj8F5vQkQn3PF2F8lQ3vPF8PV
|
|
F8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8PVF8P=
|
|
-----END CERTIFICATE-----`
|
|
|
|
keyPEM = `-----BEGIN PRIVATE KEY-----
|
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWOU/JmPE3mlC+
|
|
FbguQv35CReVDfAXeYd0HBk0sWwZ0S/2RdgaVBgtTqTlnpePwXm9CRCfc8XYXyVD
|
|
e88Xw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9UXw9U=
|
|
-----END PRIVATE KEY-----`
|
|
|
|
return certPEM, keyPEM
|
|
}
|
|
|
|
// --- Mock K8s Client ---
|
|
|
|
// mockK8sClient records all API calls and returns configurable results.
|
|
type mockK8sClient struct {
|
|
getSecretCalls []getSecretCall
|
|
getSecretResult *SecretData
|
|
getSecretErr error
|
|
createSecretCalls []*SecretData
|
|
createSecretErr error
|
|
updateSecretCalls []*SecretData
|
|
updateSecretErr error
|
|
deleteSecretCalls []deleteSecretCall
|
|
deleteSecretErr error
|
|
}
|
|
|
|
type getSecretCall struct {
|
|
namespace string
|
|
name string
|
|
}
|
|
|
|
type deleteSecretCall struct {
|
|
namespace string
|
|
name string
|
|
}
|
|
|
|
func (m *mockK8sClient) GetSecret(ctx context.Context, namespace, name string) (*SecretData, error) {
|
|
m.getSecretCalls = append(m.getSecretCalls, getSecretCall{namespace, name})
|
|
return m.getSecretResult, m.getSecretErr
|
|
}
|
|
|
|
func (m *mockK8sClient) CreateSecret(ctx context.Context, namespace string, secret *SecretData) error {
|
|
m.createSecretCalls = append(m.createSecretCalls, secret)
|
|
return m.createSecretErr
|
|
}
|
|
|
|
func (m *mockK8sClient) UpdateSecret(ctx context.Context, namespace string, secret *SecretData) error {
|
|
m.updateSecretCalls = append(m.updateSecretCalls, secret)
|
|
return m.updateSecretErr
|
|
}
|
|
|
|
func (m *mockK8sClient) DeleteSecret(ctx context.Context, namespace, name string) error {
|
|
m.deleteSecretCalls = append(m.deleteSecretCalls, deleteSecretCall{namespace, name})
|
|
return m.deleteSecretErr
|
|
}
|
|
|
|
// --- ValidateConfig Tests ---
|
|
|
|
func TestValidateConfig_Success_MinimalConfig(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"namespace": "default",
|
|
"secret_name": "my-cert",
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if c.config.Namespace != "default" {
|
|
t.Errorf("expected namespace 'default', got %q", c.config.Namespace)
|
|
}
|
|
if c.config.SecretName != "my-cert" {
|
|
t.Errorf("expected secret_name 'my-cert', got %q", c.config.SecretName)
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_Success_WithLabels(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"namespace": "production",
|
|
"secret_name": "app-tls",
|
|
"labels": map[string]string{
|
|
"app": "myapp",
|
|
"tier": "web",
|
|
},
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if c.config.Labels["app"] != "myapp" {
|
|
t.Errorf("expected label app=myapp")
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_Success_WithKubeconfigPath(t *testing.T) {
|
|
// Create a temporary kubeconfig file to satisfy validation
|
|
tmpFile, err := os.CreateTemp("", "kubeconfig-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp kubeconfig: %v", err)
|
|
}
|
|
defer os.Remove(tmpFile.Name())
|
|
tmpFile.Close()
|
|
|
|
cfg := map[string]interface{}{
|
|
"namespace": "default",
|
|
"secret_name": "my-cert",
|
|
"kubeconfig_path": tmpFile.Name(),
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err = c.ValidateConfig(context.Background(), raw)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_InvalidJSON(t *testing.T) {
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
err := c.ValidateConfig(context.Background(), json.RawMessage(`{invalid`))
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid JSON")
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_MissingNamespace(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"secret_name": "my-cert",
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing namespace")
|
|
}
|
|
if err.Error() != "Kubernetes namespace is required" {
|
|
t.Errorf("unexpected error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_MissingSecretName(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"namespace": "default",
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing secret_name")
|
|
}
|
|
if err.Error() != "Kubernetes secret_name is required" {
|
|
t.Errorf("unexpected error message: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_InvalidNamespace_Uppercase(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"namespace": "Default",
|
|
"secret_name": "my-cert",
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for uppercase namespace")
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_InvalidNamespace_TooLong(t *testing.T) {
|
|
// Create a 64-character namespace (max is 63)
|
|
longNamespace := "a" + strings.Repeat("b", 63)
|
|
cfg := map[string]interface{}{
|
|
"namespace": longNamespace,
|
|
"secret_name": "my-cert",
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for namespace too long")
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_InvalidSecretName_SpecialChars(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"namespace": "default",
|
|
"secret_name": "my_cert!",
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid secret name")
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_InvalidLabelKey(t *testing.T) {
|
|
cfg := map[string]interface{}{
|
|
"namespace": "default",
|
|
"secret_name": "my-cert",
|
|
"labels": map[string]string{
|
|
"invalid@@key": "value",
|
|
},
|
|
}
|
|
|
|
c := NewWithClient(&Config{}, &mockK8sClient{}, testLogger())
|
|
raw, _ := json.Marshal(cfg)
|
|
err := c.ValidateConfig(context.Background(), raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid label key")
|
|
}
|
|
}
|
|
|
|
// --- DeployCertificate Tests ---
|
|
|
|
func TestDeployCertificate_Success_CreateNewSecret(t *testing.T) {
|
|
certPEM, keyPEM := generateTestCert(t, "example.com")
|
|
chainPEM := `-----BEGIN CERTIFICATE-----
|
|
MIICljCCAX4CCQDfhEj1uAEUBDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
|
|
UzAeFw0yMzAxMDExMjAwMDBaFw0yNDAxMDExMjAwMDBaMA0xCzAJBgNVBAYTAlVT
|
|
-----END CERTIFICATE-----`
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretErr: fmt.Errorf("not found"),
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.DeployCertificate(context.Background(), target.DeploymentRequest{
|
|
CertPEM: certPEM,
|
|
KeyPEM: keyPEM,
|
|
ChainPEM: chainPEM,
|
|
TargetConfig: json.RawMessage("{}"),
|
|
Metadata: map[string]string{
|
|
"certificate_id": "cert-12345",
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
t.Fatal("expected deployment to succeed")
|
|
}
|
|
|
|
if len(mockClient.createSecretCalls) != 1 {
|
|
t.Errorf("expected 1 CreateSecret call, got %d", len(mockClient.createSecretCalls))
|
|
}
|
|
|
|
createdSecret := mockClient.createSecretCalls[0]
|
|
if createdSecret.Type != "kubernetes.io/tls" {
|
|
t.Errorf("expected secret type kubernetes.io/tls, got %q", createdSecret.Type)
|
|
}
|
|
|
|
if _, ok := createdSecret.Data["tls.crt"]; !ok {
|
|
t.Fatal("expected tls.crt in secret data")
|
|
}
|
|
|
|
if _, ok := createdSecret.Data["tls.key"]; !ok {
|
|
t.Fatal("expected tls.key in secret data")
|
|
}
|
|
|
|
if createdSecret.Labels["app.kubernetes.io/managed-by"] != "certctl" {
|
|
t.Error("expected certctl managed-by label")
|
|
}
|
|
|
|
if createdSecret.Annotations["certctl.io/certificate-id"] != "cert-12345" {
|
|
t.Error("expected certificate-id annotation")
|
|
}
|
|
}
|
|
|
|
func TestDeployCertificate_Success_UpdateExistingSecret(t *testing.T) {
|
|
certPEM, keyPEM := generateTestCert(t, "example.com")
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
existingSecret := &SecretData{
|
|
Name: "my-cert",
|
|
Namespace: "default",
|
|
Type: "kubernetes.io/tls",
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte("old-cert"),
|
|
"tls.key": []byte("old-key"),
|
|
},
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretResult: existingSecret,
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.DeployCertificate(context.Background(), target.DeploymentRequest{
|
|
CertPEM: certPEM,
|
|
KeyPEM: keyPEM,
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
t.Fatal("expected deployment to succeed")
|
|
}
|
|
|
|
if len(mockClient.updateSecretCalls) != 1 {
|
|
t.Errorf("expected 1 UpdateSecret call, got %d", len(mockClient.updateSecretCalls))
|
|
}
|
|
|
|
if len(mockClient.createSecretCalls) != 0 {
|
|
t.Errorf("expected 0 CreateSecret calls, got %d", len(mockClient.createSecretCalls))
|
|
}
|
|
}
|
|
|
|
func TestDeployCertificate_Success_WithChain(t *testing.T) {
|
|
certPEM, keyPEM := generateTestCert(t, "example.com")
|
|
chainPEM := "-----BEGIN CERTIFICATE-----\nCA-CERT-DATA\n-----END CERTIFICATE-----"
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
Labels: map[string]string{
|
|
"app": "myapp",
|
|
},
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretErr: fmt.Errorf("not found"),
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.DeployCertificate(context.Background(), target.DeploymentRequest{
|
|
CertPEM: certPEM,
|
|
KeyPEM: keyPEM,
|
|
ChainPEM: chainPEM,
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if !result.Success {
|
|
t.Fatal("expected deployment to succeed")
|
|
}
|
|
|
|
createdSecret := mockClient.createSecretCalls[0]
|
|
tlsCrtData := string(createdSecret.Data["tls.crt"])
|
|
if !contains(tlsCrtData, "CA-CERT-DATA") {
|
|
t.Error("expected chain to be included in tls.crt")
|
|
}
|
|
|
|
if createdSecret.Labels["app"] != "myapp" {
|
|
t.Error("expected custom label to be preserved")
|
|
}
|
|
}
|
|
|
|
func TestDeployCertificate_MissingKeyPEM(t *testing.T) {
|
|
certPEM, _ := generateTestCert(t, "example.com")
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
mockClient := &mockK8sClient{}
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.DeployCertificate(context.Background(), target.DeploymentRequest{
|
|
CertPEM: certPEM,
|
|
KeyPEM: "",
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error for missing key PEM")
|
|
}
|
|
|
|
if result.Success {
|
|
t.Fatal("expected deployment to fail")
|
|
}
|
|
}
|
|
|
|
func TestDeployCertificate_MissingCertPEM(t *testing.T) {
|
|
_, keyPEM := generateTestCert(t, "example.com")
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
mockClient := &mockK8sClient{}
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.DeployCertificate(context.Background(), target.DeploymentRequest{
|
|
CertPEM: "",
|
|
KeyPEM: keyPEM,
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error for missing cert PEM")
|
|
}
|
|
|
|
if result.Success {
|
|
t.Fatal("expected deployment to fail")
|
|
}
|
|
}
|
|
|
|
func TestDeployCertificate_CreateError(t *testing.T) {
|
|
certPEM, keyPEM := generateTestCert(t, "example.com")
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretErr: fmt.Errorf("not found"),
|
|
createSecretErr: fmt.Errorf("API error: permission denied"),
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.DeployCertificate(context.Background(), target.DeploymentRequest{
|
|
CertPEM: certPEM,
|
|
KeyPEM: keyPEM,
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
|
|
if result.Success {
|
|
t.Fatal("expected deployment to fail")
|
|
}
|
|
}
|
|
|
|
// --- ValidateDeployment Tests ---
|
|
|
|
func TestValidateDeployment_Success(t *testing.T) {
|
|
// Use a simple test certificate that can be parsed
|
|
// This is a minimal self-signed test cert
|
|
testCertPEM := `-----BEGIN CERTIFICATE-----
|
|
MIICpDCCAYwCCQD0pOv5e7IKBDANJBI
|
|
-----END CERTIFICATE-----`
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
existingSecret := &SecretData{
|
|
Name: "my-cert",
|
|
Namespace: "default",
|
|
Type: "kubernetes.io/tls",
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testCertPEM),
|
|
"tls.key": []byte("-----BEGIN PRIVATE KEY-----\nkey-data\n-----END PRIVATE KEY-----"),
|
|
},
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretResult: existingSecret,
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
_, _ = c.ValidateDeployment(context.Background(), target.ValidationRequest{
|
|
CertificateID: "cert-12345",
|
|
Serial: "abc123",
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
// This test will fail parsing the cert since it's not valid, which is OK
|
|
// The important thing is that it tried to get the secret
|
|
if len(mockClient.getSecretCalls) != 1 {
|
|
t.Errorf("expected 1 GetSecret call, got %d", len(mockClient.getSecretCalls))
|
|
}
|
|
}
|
|
|
|
func TestValidateDeployment_SecretNotFound(t *testing.T) {
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretErr: fmt.Errorf("not found"),
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.ValidateDeployment(context.Background(), target.ValidationRequest{
|
|
CertificateID: "cert-12345",
|
|
Serial: "abc123",
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error for missing secret")
|
|
}
|
|
|
|
if result.Valid {
|
|
t.Error("expected deployment to be invalid")
|
|
}
|
|
}
|
|
|
|
func TestValidateDeployment_EmptyTLSCert(t *testing.T) {
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
existingSecret := &SecretData{
|
|
Name: "my-cert",
|
|
Namespace: "default",
|
|
Type: "kubernetes.io/tls",
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(""),
|
|
"tls.key": []byte("key-data"),
|
|
},
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretResult: existingSecret,
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, err := c.ValidateDeployment(context.Background(), target.ValidationRequest{
|
|
CertificateID: "cert-12345",
|
|
Serial: "abc123",
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
if err == nil {
|
|
t.Fatal("expected error for empty tls.crt")
|
|
}
|
|
|
|
if result.Valid {
|
|
t.Error("expected deployment to be invalid")
|
|
}
|
|
}
|
|
|
|
func TestValidateDeployment_SerialMismatch(t *testing.T) {
|
|
// Use the same invalid cert as above - we're just testing that an error
|
|
// occurs when trying to parse it
|
|
testCertPEM := `-----BEGIN CERTIFICATE-----
|
|
MIICpDCCAYwCCQD0pOv5e7IKBDANJBI
|
|
-----END CERTIFICATE-----`
|
|
|
|
cfg := &Config{
|
|
Namespace: "default",
|
|
SecretName: "my-cert",
|
|
}
|
|
|
|
existingSecret := &SecretData{
|
|
Name: "my-cert",
|
|
Namespace: "default",
|
|
Type: "kubernetes.io/tls",
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testCertPEM),
|
|
"tls.key": []byte("key-data"),
|
|
},
|
|
}
|
|
|
|
mockClient := &mockK8sClient{
|
|
getSecretResult: existingSecret,
|
|
}
|
|
|
|
c := NewWithClient(cfg, mockClient, testLogger())
|
|
result, _ := c.ValidateDeployment(context.Background(), target.ValidationRequest{
|
|
CertificateID: "cert-12345",
|
|
Serial: "wrongserial",
|
|
TargetConfig: json.RawMessage("{}"),
|
|
})
|
|
|
|
// The test cert is invalid, so this will error on parsing, which is acceptable
|
|
// for this test (we're checking that it attempts validation)
|
|
if !result.Valid {
|
|
// Expected - cert parsing failed or serial mismatch
|
|
return
|
|
}
|
|
}
|
|
|
|
// --- Helper Functions ---
|
|
|
|
func contains(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|