mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:01:30 +00:00
docs: rewrite features.md, audit README + architecture against repo
Rewrote docs/features.md from scratch as authoritative feature inventory (1255 lines, every claim verified against source files). Audited README.md and architecture.md against repo — fixed 19 stale references: K8s Secrets status, issuer counts, dashboard page counts, CI thresholds, missing connectors in Mermaid diagrams, OpenAPI operation count, GetCACertPEM behavior, and V2/V4 roadmap accuracy. Also includes related fixes discovered during audit: - Scheduler skips expired/failed/revoked certs from auto-renewal - Seed demo expiry dates moved outside 31-day scheduler query window - Agent pages use correct last_heartbeat_at field name Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -136,8 +136,17 @@ func (s *RenewalService) CheckExpiringCertificates(ctx context.Context) error {
|
||||
policyCache := make(map[string]*domain.RenewalPolicy)
|
||||
|
||||
for _, cert := range expiring {
|
||||
// Skip if already renewing or archived
|
||||
if cert.Status == domain.CertificateStatusRenewalInProgress || cert.Status == domain.CertificateStatusArchived {
|
||||
// Skip certs in terminal or non-renewable states:
|
||||
// - RenewalInProgress: already being renewed
|
||||
// - Archived: no longer managed
|
||||
// - Revoked: intentionally revoked, should not be auto-renewed
|
||||
// - Failed: requires manual intervention (the failure cause hasn't been resolved)
|
||||
// - Expired: requires manual review (why did it expire without renewal?)
|
||||
if cert.Status == domain.CertificateStatusRenewalInProgress ||
|
||||
cert.Status == domain.CertificateStatusArchived ||
|
||||
cert.Status == domain.CertificateStatusRevoked ||
|
||||
cert.Status == domain.CertificateStatusFailed ||
|
||||
cert.Status == domain.CertificateStatusExpired {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,77 @@ func TestCheckExpiringCertificates_SkipsRenewalInProgress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckExpiringCertificates_SkipsExpiredFailedRevoked(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Test that certs in Expired, Failed, and Revoked states do not get renewal jobs
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
status domain.CertificateStatus
|
||||
}{
|
||||
{"Expired", domain.CertificateStatusExpired},
|
||||
{"Failed", domain.CertificateStatusFailed},
|
||||
{"Revoked", domain.CertificateStatusRevoked},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
certRepo := newMockCertificateRepository()
|
||||
jobRepo := newMockJobRepository()
|
||||
policyRepo := newMockRenewalPolicyRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
notifRepo := newMockNotificationRepository()
|
||||
|
||||
auditSvc := NewAuditService(auditRepo)
|
||||
notifSvc := NewNotificationService(notifRepo, map[string]Notifier{})
|
||||
|
||||
issuerRegistry := NewIssuerRegistry(slog.Default())
|
||||
issuerRegistry.Set("iss-test", &mockIssuerConnector{})
|
||||
|
||||
svc := NewRenewalService(certRepo, jobRepo, policyRepo, nil, auditSvc, notifSvc, issuerRegistry, "server")
|
||||
|
||||
cert := &domain.ManagedCertificate{
|
||||
ID: "mc-" + strings.ToLower(string(tc.status)),
|
||||
Name: "Test " + string(tc.status),
|
||||
CommonName: "test.example.com",
|
||||
SANs: []string{},
|
||||
OwnerID: "owner-1",
|
||||
TeamID: "team-1",
|
||||
IssuerID: "iss-test",
|
||||
RenewalPolicyID: "rp-standard",
|
||||
Status: tc.status,
|
||||
ExpiresAt: time.Now().AddDate(0, 0, 10),
|
||||
Tags: make(map[string]string),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
certRepo.AddCert(cert)
|
||||
|
||||
policy := &domain.RenewalPolicy{
|
||||
ID: "rp-standard",
|
||||
Name: "Standard",
|
||||
RenewalWindowDays: 30,
|
||||
AutoRenew: true,
|
||||
MaxRetries: 3,
|
||||
RetryInterval: 300,
|
||||
AlertThresholdsDays: []int{30, 14, 7, 0},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
policyRepo.AddPolicy(policy)
|
||||
|
||||
err := svc.CheckExpiringCertificates(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckExpiringCertificates failed: %v", err)
|
||||
}
|
||||
|
||||
for _, job := range jobRepo.Jobs {
|
||||
if job.Type == domain.JobTypeRenewal {
|
||||
t.Errorf("should not create renewal job for cert with %s status", tc.status)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckExpiringCertificates_UpdatesStatusToExpiring(t *testing.T) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user