diff --git a/cmd/agent/agent_test.go b/cmd/agent/agent_test.go index 6f0256b..a6df2cc 100644 --- a/cmd/agent/agent_test.go +++ b/cmd/agent/agent_test.go @@ -692,10 +692,10 @@ func TestMakeRequest_InvalidURL(t *testing.T) { // TestCertKeyInfo tests extraction of key algorithm and size from certificates. func TestCertKeyInfo(t *testing.T) { tests := []struct { - name string - genKey func() interface{} - expectedAlg string - minBitSize int + name string + genKey func() interface{} + expectedAlg string + minBitSize int }{ { name: "ECDSA P-256", @@ -1503,9 +1503,9 @@ func TestValidateHTTPSScheme(t *testing.T) { wantErrSub: "plaintext http://", }, { - name: "bare host missing scheme falls through to unsupported", - serverURL: "localhost:8443", - wantErr: true, + name: "bare host missing scheme falls through to unsupported", + serverURL: "localhost:8443", + wantErr: true, // url.Parse treats "localhost:8443" as scheme=localhost, // opaque=8443 — exercises the default arm (unsupported scheme) // rather than the empty-scheme arm. Both are fail-closed, which diff --git a/cmd/agent/main.go b/cmd/agent/main.go index fbd1739..3f96fa4 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -34,16 +34,16 @@ import ( "github.com/shankar0123/certctl/internal/connector/target/apache" "github.com/shankar0123/certctl/internal/connector/target/caddy" "github.com/shankar0123/certctl/internal/connector/target/envoy" - pf "github.com/shankar0123/certctl/internal/connector/target/postfix" - sshconn "github.com/shankar0123/certctl/internal/connector/target/ssh" "github.com/shankar0123/certctl/internal/connector/target/f5" - jks "github.com/shankar0123/certctl/internal/connector/target/javakeystore" - k8s "github.com/shankar0123/certctl/internal/connector/target/k8ssecret" - wcs "github.com/shankar0123/certctl/internal/connector/target/wincertstore" "github.com/shankar0123/certctl/internal/connector/target/haproxy" "github.com/shankar0123/certctl/internal/connector/target/iis" + jks "github.com/shankar0123/certctl/internal/connector/target/javakeystore" + k8s "github.com/shankar0123/certctl/internal/connector/target/k8ssecret" "github.com/shankar0123/certctl/internal/connector/target/nginx" + pf "github.com/shankar0123/certctl/internal/connector/target/postfix" + sshconn "github.com/shankar0123/certctl/internal/connector/target/ssh" "github.com/shankar0123/certctl/internal/connector/target/traefik" + wcs "github.com/shankar0123/certctl/internal/connector/target/wincertstore" ) // AgentConfig represents the agent-side configuration. @@ -80,10 +80,10 @@ type Agent struct { client *http.Client // Configuration - heartbeatInterval time.Duration - pollInterval time.Duration - discoveryInterval time.Duration - consecutiveFailures int + heartbeatInterval time.Duration + pollInterval time.Duration + discoveryInterval time.Duration + consecutiveFailures int // I-004: terminal retirement signal. retiredSignal is closed exactly once // (guarded by retiredOnce) when either sendHeartbeat or pollForWork diff --git a/cmd/agent/verify.go b/cmd/agent/verify.go index e3cbede..2e9de41 100644 --- a/cmd/agent/verify.go +++ b/cmd/agent/verify.go @@ -75,8 +75,8 @@ func verifyDeployment( // calls, issuer connector communication, or any operation that trusts the // certificate. The verification result compares SHA-256 fingerprints only. // See TICKET-016 for full security audit rationale. - InsecureSkipVerify: true, //nolint:gosec // verification probe; documented above + docs/tls.md L-001 table - ServerName: targetHost, // For SNI + InsecureSkipVerify: true, //nolint:gosec // verification probe; documented above + docs/tls.md L-001 table + ServerName: targetHost, // For SNI }) if err != nil { return nil, fmt.Errorf("failed to connect to %s: %w", address, err) @@ -161,11 +161,11 @@ func (a *Agent) reportVerificationResult( // Build the request payload payload := map[string]interface{}{ - "target_id": targetID, - "expected_fingerprint": result.ExpectedFingerprint, - "actual_fingerprint": result.ActualFingerprint, - "verified": result.Verified, - "error": result.Error, + "target_id": targetID, + "expected_fingerprint": result.ExpectedFingerprint, + "actual_fingerprint": result.ActualFingerprint, + "verified": result.Verified, + "error": result.Error, } body, err := json.Marshal(payload) @@ -247,7 +247,7 @@ func (a *Agent) verifyAndReportDeployment( ) { // Perform verification with configured timeout and delay result, err := verifyDeployment(ctx, targetHost, targetPort, certPEM, - 2*time.Second, // delay before probing + 2*time.Second, // delay before probing 10*time.Second, // timeout for TLS connection a.logger) @@ -261,7 +261,7 @@ func (a *Agent) verifyAndReportDeployment( } // Probe failure: report error but continue result = &VerificationResult{ - Error: err.Error(), + Error: err.Error(), VerifiedAt: time.Now().UTC(), } } diff --git a/cmd/agent/verify_test.go b/cmd/agent/verify_test.go index e53adb9..b23a7fe 100644 --- a/cmd/agent/verify_test.go +++ b/cmd/agent/verify_test.go @@ -114,9 +114,9 @@ func TestExtractTargetHostAndPort_InvalidJSON(t *testing.T) { func TestExtractTargetHostAndPort_AlternativeFieldNames(t *testing.T) { tests := []struct { - name string - config map[string]interface{} - expected string + name string + config map[string]interface{} + expected string }{ {"host", map[string]interface{}{"host": "host1.com"}, "host1.com"}, {"hostname", map[string]interface{}{"hostname": "host2.com"}, "host2.com"}, diff --git a/cmd/cli/main_test.go b/cmd/cli/main_test.go index 87df0b4..6a12038 100644 --- a/cmd/cli/main_test.go +++ b/cmd/cli/main_test.go @@ -53,9 +53,9 @@ func TestValidateHTTPSScheme(t *testing.T) { wantErrSub: "plaintext http://", }, { - name: "bare host missing scheme rejected", - serverURL: "localhost:8443", - wantErr: true, + name: "bare host missing scheme rejected", + serverURL: "localhost:8443", + wantErr: true, // url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443 // — exercises the default arm (unsupported scheme) rather than the // empty-scheme arm. Both are fail-closed, which is what we care about. diff --git a/cmd/mcp-server/main_test.go b/cmd/mcp-server/main_test.go index 7a96d46..a193b72 100644 --- a/cmd/mcp-server/main_test.go +++ b/cmd/mcp-server/main_test.go @@ -47,9 +47,9 @@ func TestValidateHTTPSScheme(t *testing.T) { wantErrSub: "plaintext http://", }, { - name: "bare host missing scheme rejected", - serverURL: "localhost:8443", - wantErr: true, + name: "bare host missing scheme rejected", + serverURL: "localhost:8443", + wantErr: true, // url.Parse treats "localhost:8443" as scheme=localhost, opaque=8443 // — exercises the default arm (unsupported scheme) rather than the // empty-scheme arm. Both are fail-closed, which is what we care about. diff --git a/deploy/test/qa_test.go b/deploy/test/qa_test.go index 634ba76..b1d3892 100644 --- a/deploy/test/qa_test.go +++ b/deploy/test/qa_test.go @@ -149,10 +149,10 @@ func (c *qaClient) do(method, path string, body string) (*http.Response, error) return c.http.Do(req) } -func (c *qaClient) get(path string) (*http.Response, error) { return c.do("GET", path, "") } -func (c *qaClient) post(path, body string) (*http.Response, error) { return c.do("POST", path, body) } -func (c *qaClient) put(path, body string) (*http.Response, error) { return c.do("PUT", path, body) } -func (c *qaClient) delete(path string) (*http.Response, error) { return c.do("DELETE", path, "") } +func (c *qaClient) get(path string) (*http.Response, error) { return c.do("GET", path, "") } +func (c *qaClient) post(path, body string) (*http.Response, error) { return c.do("POST", path, body) } +func (c *qaClient) put(path, body string) (*http.Response, error) { return c.do("PUT", path, body) } +func (c *qaClient) delete(path string) (*http.Response, error) { return c.do("DELETE", path, "") } // statusCode makes a request and returns the HTTP status code. func (c *qaClient) statusCode(method, path, body string) (int, error) { @@ -228,11 +228,11 @@ type qaCert struct { } type qaJob struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` - CertificateID string `json:"certificate_id"` - AgentID *string `json:"agent_id"` + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + CertificateID string `json:"certificate_id"` + AgentID *string `json:"agent_id"` } type qaIssuer struct { @@ -261,15 +261,15 @@ type qaAgent struct { } type qaNotification struct { - ID string `json:"id"` - Read bool `json:"read"` + ID string `json:"id"` + Read bool `json:"read"` } type qaStats struct { - TotalCertificates int `json:"total_certificates"` - ActiveCertificates int `json:"active_certificates"` + TotalCertificates int `json:"total_certificates"` + ActiveCertificates int `json:"active_certificates"` ExpiringCertificates int `json:"expiring_certificates"` - TotalAgents int `json:"total_agents"` + TotalAgents int `json:"total_agents"` } type qaMetrics struct { diff --git a/internal/api/handler/adversarial_path_test.go b/internal/api/handler/adversarial_path_test.go index 668afe6..acc51dd 100644 --- a/internal/api/handler/adversarial_path_test.go +++ b/internal/api/handler/adversarial_path_test.go @@ -56,8 +56,8 @@ func adversarialPathInputs() []struct { {"null_byte_trailing", "mc-001\x00"}, {"null_byte_embedded", "mc-\x00-001"}, {"long_id_10k", strings.Repeat("A", 10000)}, - {"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN - {"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS + {"unicode_homoglyph_hyphen", "mc\u2010001"}, // U+2010 HYPHEN + {"unicode_homoglyph_fullwidth", "mc\uFF0D001"}, // U+FF0D FULLWIDTH HYPHEN-MINUS {"control_char_newline", "mc-001\n"}, {"control_char_tab", "mc\t001"}, {"control_char_bell", "mc\x07001"}, diff --git a/internal/api/handler/agent_groups.go b/internal/api/handler/agent_groups.go index 85f0cf5..f69237b 100644 --- a/internal/api/handler/agent_groups.go +++ b/internal/api/handler/agent_groups.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" - "errors" "context" "encoding/json" + "errors" + "github.com/shankar0123/certctl/internal/repository" "net/http" "strconv" "strings" diff --git a/internal/api/handler/agent_handler_test.go b/internal/api/handler/agent_handler_test.go index fc277c8..92e5243 100644 --- a/internal/api/handler/agent_handler_test.go +++ b/internal/api/handler/agent_handler_test.go @@ -28,8 +28,8 @@ type MockAgentService struct { // I-004: soft-retirement hooks. Tests that don't set these receive nil // results and nil errors, which mirrors the safest default (no-op) for // unrelated suites that mock only the legacy surface. - RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error) - ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error) + RetireAgentFn func(agentID, actor string, force bool, reason string) (*service.AgentRetirementResult, error) + ListRetiredAgentsFn func(page, perPage int) ([]domain.Agent, int64, error) } func (m *MockAgentService) ListAgents(_ context.Context, page, perPage int) ([]domain.Agent, int64, error) { diff --git a/internal/api/handler/agent_retire_handler_test.go b/internal/api/handler/agent_retire_handler_test.go index 46233a4..09c3ad6 100644 --- a/internal/api/handler/agent_retire_handler_test.go +++ b/internal/api/handler/agent_retire_handler_test.go @@ -56,10 +56,10 @@ func TestRetireAgentHandler_Success_200(t *testing.T) { } var body struct { - RetiredAt time.Time `json:"retired_at"` - AlreadyRetired bool `json:"already_retired"` - Cascade bool `json:"cascade"` - Counts domain.AgentDependencyCounts `json:"counts"` + RetiredAt time.Time `json:"retired_at"` + AlreadyRetired bool `json:"already_retired"` + Cascade bool `json:"cascade"` + Counts domain.AgentDependencyCounts `json:"counts"` } if err := json.NewDecoder(w.Body).Decode(&body); err != nil { t.Fatalf("decode 200 body: %v", err) @@ -273,10 +273,10 @@ func TestRetireAgentHandler_ForceCascade_200(t *testing.T) { } var body struct { - RetiredAt time.Time `json:"retired_at"` - AlreadyRetired bool `json:"already_retired"` - Cascade bool `json:"cascade"` - Counts domain.AgentDependencyCounts `json:"counts"` + RetiredAt time.Time `json:"retired_at"` + AlreadyRetired bool `json:"already_retired"` + Cascade bool `json:"cascade"` + Counts domain.AgentDependencyCounts `json:"counts"` } if err := json.NewDecoder(w.Body).Decode(&body); err != nil { t.Fatalf("decode force-cascade 200 body: %v", err) diff --git a/internal/api/handler/agents.go b/internal/api/handler/agents.go index 2fd8701..6d47a99 100644 --- a/internal/api/handler/agents.go +++ b/internal/api/handler/agents.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" "context" "encoding/json" "errors" + "github.com/shankar0123/certctl/internal/repository" "log/slog" "net/http" "strconv" diff --git a/internal/api/handler/audit_handler_test.go b/internal/api/handler/audit_handler_test.go index 7e3afb5..d09f4e0 100644 --- a/internal/api/handler/audit_handler_test.go +++ b/internal/api/handler/audit_handler_test.go @@ -9,14 +9,14 @@ import ( "testing" "time" - "github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/api/middleware" + "github.com/shankar0123/certctl/internal/domain" ) // mockAuditService implements AuditService for testing. type mockAuditService struct { - listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error) - getFunc func(id string) (*domain.AuditEvent, error) + listFunc func(page, perPage int) ([]domain.AuditEvent, int64, error) + getFunc func(id string) (*domain.AuditEvent, error) } func (m *mockAuditService) ListAuditEvents(_ context.Context, page, perPage int) ([]domain.AuditEvent, int64, error) { diff --git a/internal/api/handler/bulk_partial_failure_test.go b/internal/api/handler/bulk_partial_failure_test.go index ef00977..2dec184 100644 --- a/internal/api/handler/bulk_partial_failure_test.go +++ b/internal/api/handler/bulk_partial_failure_test.go @@ -86,10 +86,10 @@ func TestBulkRenew_PartialFailure_ReportsBoth(t *testing.T) { svc := &mockBulkRenewalService{ BulkRenewFn: func(ctx context.Context, criteria domain.BulkRenewalCriteria, actor string) (*domain.BulkRenewalResult, error) { return &domain.BulkRenewalResult{ - TotalMatched: 3, + TotalMatched: 3, TotalEnqueued: 2, - TotalSkipped: 0, - TotalFailed: 1, + TotalSkipped: 0, + TotalFailed: 1, Errors: []domain.BulkOperationError{ {CertificateID: "mc-failed", Error: "renewal job enqueue failed: db timeout"}, }, diff --git a/internal/api/handler/est_handler_test.go b/internal/api/handler/est_handler_test.go index 19d112c..a198083 100644 --- a/internal/api/handler/est_handler_test.go +++ b/internal/api/handler/est_handler_test.go @@ -98,12 +98,12 @@ func generateTestCertPEM(t *testing.T) string { t.Fatalf("failed to generate key: %v", err) } template := &x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: "Test CA"}, - NotBefore: time.Now().Add(-1 * time.Hour), - NotAfter: time.Now().Add(24 * time.Hour), - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, - IsCA: true, + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "Test CA"}, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + IsCA: true, BasicConstraintsValid: true, } certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) diff --git a/internal/api/handler/export.go b/internal/api/handler/export.go index 0e3cfc2..96214c0 100644 --- a/internal/api/handler/export.go +++ b/internal/api/handler/export.go @@ -1,11 +1,11 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" - "errors" "context" "encoding/json" + "errors" "fmt" + "github.com/shankar0123/certctl/internal/repository" "log/slog" "net/http" "strings" diff --git a/internal/api/handler/health_check.go b/internal/api/handler/health_check.go index 2f325a0..5af017c 100644 --- a/internal/api/handler/health_check.go +++ b/internal/api/handler/health_check.go @@ -59,12 +59,12 @@ func (h *HealthCheckHandler) ListHealthChecks(w http.ResponseWriter, r *http.Req } filter := &repository.HealthCheckFilter{ - Status: status, - CertificateID: certificateID, - NetworkScanTargetID: networkScanTargetID, - Enabled: enabledFilter, - Page: page, - PerPage: perPage, + Status: status, + CertificateID: certificateID, + NetworkScanTargetID: networkScanTargetID, + Enabled: enabledFilter, + Page: page, + PerPage: perPage, } checks, total, err := h.service.List(r.Context(), filter) diff --git a/internal/api/handler/issuers.go b/internal/api/handler/issuers.go index ab81204..153380e 100644 --- a/internal/api/handler/issuers.go +++ b/internal/api/handler/issuers.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" - "errors" "context" "encoding/json" + "errors" + "github.com/shankar0123/certctl/internal/repository" "log/slog" "net/http" "strconv" diff --git a/internal/api/handler/jobs.go b/internal/api/handler/jobs.go index 235c1ac..649fbce 100644 --- a/internal/api/handler/jobs.go +++ b/internal/api/handler/jobs.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" "context" "encoding/json" "errors" + "github.com/shankar0123/certctl/internal/repository" "io" "net/http" "strconv" diff --git a/internal/api/handler/notifications.go b/internal/api/handler/notifications.go index 85c115b..c761f2b 100644 --- a/internal/api/handler/notifications.go +++ b/internal/api/handler/notifications.go @@ -1,9 +1,9 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" - "errors" "context" + "errors" + "github.com/shankar0123/certctl/internal/repository" "net/http" "strconv" "strings" diff --git a/internal/api/handler/owners.go b/internal/api/handler/owners.go index 7489112..5530917 100644 --- a/internal/api/handler/owners.go +++ b/internal/api/handler/owners.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" - "errors" "context" "encoding/json" + "errors" + "github.com/shankar0123/certctl/internal/repository" "net/http" "strconv" "strings" diff --git a/internal/api/handler/profile_handler_test.go b/internal/api/handler/profile_handler_test.go index e2fe204..a523e7d 100644 --- a/internal/api/handler/profile_handler_test.go +++ b/internal/api/handler/profile_handler_test.go @@ -237,9 +237,9 @@ func TestCreateProfile_Success(t *testing.T) { } body := map[string]interface{}{ - "name": "New Profile", + "name": "New Profile", "max_ttl_seconds": 86400, - "allowed_ekus": []string{"serverAuth"}, + "allowed_ekus": []string{"serverAuth"}, } bodyBytes, _ := json.Marshal(body) @@ -331,7 +331,7 @@ func TestUpdateProfile_Success(t *testing.T) { } body := map[string]interface{}{ - "name": "Updated Profile", + "name": "Updated Profile", "max_ttl_seconds": 172800, } bodyBytes, _ := json.Marshal(body) diff --git a/internal/api/handler/profiles.go b/internal/api/handler/profiles.go index 93302eb..118d679 100644 --- a/internal/api/handler/profiles.go +++ b/internal/api/handler/profiles.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" - "errors" "context" "encoding/json" + "errors" + "github.com/shankar0123/certctl/internal/repository" "net/http" "strconv" "strings" diff --git a/internal/api/handler/renewal_policy.go b/internal/api/handler/renewal_policy.go index d95e606..8c36415 100644 --- a/internal/api/handler/renewal_policy.go +++ b/internal/api/handler/renewal_policy.go @@ -1,10 +1,10 @@ package handler import ( - "github.com/shankar0123/certctl/internal/repository" "context" "encoding/json" "errors" + "github.com/shankar0123/certctl/internal/repository" "net/http" "strconv" "strings" diff --git a/internal/api/handler/renewal_policy_handler_test.go b/internal/api/handler/renewal_policy_handler_test.go index 1aee979..1868254 100644 --- a/internal/api/handler/renewal_policy_handler_test.go +++ b/internal/api/handler/renewal_policy_handler_test.go @@ -26,11 +26,11 @@ import ( // MockRenewalPolicyService is a mock implementation of RenewalPolicyService. type MockRenewalPolicyService struct { - ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error) - GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error) - CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) - UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) - DeleteRenewalPolicyFn func(id string) error + ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error) + GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error) + CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) + UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) + DeleteRenewalPolicyFn func(id string) error } func (m *MockRenewalPolicyService) ListRenewalPolicies(_ context.Context, page, perPage int) ([]domain.RenewalPolicy, int64, error) { @@ -199,11 +199,11 @@ func TestCreateRenewalPolicy_Success(t *testing.T) { } body := map[string]interface{}{ - "name": "New Policy", - "renewal_window_days": 30, - "max_retries": 3, + "name": "New Policy", + "renewal_window_days": 30, + "max_retries": 3, "retry_interval_seconds": 3600, - "auto_renew": true, + "auto_renew": true, } bodyBytes, _ := json.Marshal(body) @@ -221,8 +221,8 @@ func TestCreateRenewalPolicy_Success(t *testing.T) { func TestCreateRenewalPolicy_MissingName(t *testing.T) { body := map[string]interface{}{ - "renewal_window_days": 30, - "max_retries": 3, + "renewal_window_days": 30, + "max_retries": 3, "retry_interval_seconds": 3600, } bodyBytes, _ := json.Marshal(body) @@ -261,9 +261,9 @@ func TestCreateRenewalPolicy_DuplicateName(t *testing.T) { } body := map[string]interface{}{ - "name": "Duplicate", - "renewal_window_days": 30, - "max_retries": 3, + "name": "Duplicate", + "renewal_window_days": 30, + "max_retries": 3, "retry_interval_seconds": 3600, } bodyBytes, _ := json.Marshal(body) @@ -308,11 +308,11 @@ func TestUpdateRenewalPolicy_Success(t *testing.T) { } body := map[string]interface{}{ - "name": "Updated Policy", - "renewal_window_days": 45, - "max_retries": 5, + "name": "Updated Policy", + "renewal_window_days": 45, + "max_retries": 5, "retry_interval_seconds": 1800, - "auto_renew": true, + "auto_renew": true, } bodyBytes, _ := json.Marshal(body) @@ -336,9 +336,9 @@ func TestUpdateRenewalPolicy_NotFound(t *testing.T) { } body := map[string]interface{}{ - "name": "Updated", - "renewal_window_days": 30, - "max_retries": 3, + "name": "Updated", + "renewal_window_days": 30, + "max_retries": 3, "retry_interval_seconds": 3600, } bodyBytes, _ := json.Marshal(body) diff --git a/internal/api/handler/response_test.go b/internal/api/handler/response_test.go index 5a972a0..62b40cc 100644 --- a/internal/api/handler/response_test.go +++ b/internal/api/handler/response_test.go @@ -346,8 +346,8 @@ func TestCursorPagedResponse_EmptyNextCursor(t *testing.T) { func TestFilterFields_SingleObject(t *testing.T) { data := map[string]interface{}{ - "id": "cert-123", - "name": "My Cert", + "id": "cert-123", + "name": "My Cert", "expiry": "2025-01-01", "status": "active", } @@ -379,8 +379,8 @@ func TestFilterFields_SingleObject(t *testing.T) { func TestFilterFields_EmptyFields(t *testing.T) { // Empty fields list should return data unchanged data := map[string]interface{}{ - "id": "cert-123", - "name": "My Cert", + "id": "cert-123", + "name": "My Cert", } result := filterFields(data, []string{}) @@ -398,8 +398,8 @@ func TestFilterFields_EmptyFields(t *testing.T) { func TestFilterFields_NoMatchingFields(t *testing.T) { data := map[string]interface{}{ - "id": "cert-123", - "name": "My Cert", + "id": "cert-123", + "name": "My Cert", } result := filterFields(data, []string{"nonexistent", "also-not-there"}) diff --git a/internal/api/handler/validation_test.go b/internal/api/handler/validation_test.go index 45cd264..08f26c1 100644 --- a/internal/api/handler/validation_test.go +++ b/internal/api/handler/validation_test.go @@ -333,7 +333,7 @@ func TestValidateCSRPEM_InvalidInputs(t *testing.T) { // TestValidatePolicyType_ValidTypes tests valid policy types. func TestValidatePolicyType_ValidTypes(t *testing.T) { validTypes := []struct { - name string + name string ptype interface{} }{ { diff --git a/internal/api/handler/verification.go b/internal/api/handler/verification.go index 19127b0..35f4e36 100644 --- a/internal/api/handler/verification.go +++ b/internal/api/handler/verification.go @@ -97,8 +97,8 @@ func (h VerificationHandler) VerifyDeployment(w http.ResponseWriter, r *http.Req w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ - "job_id": jobID, - "verified": req.Verified, + "job_id": jobID, + "verified": req.Verified, "verified_at": result.VerifiedAt, }) } diff --git a/internal/api/middleware/audit.go b/internal/api/middleware/audit.go index 4a13172..7199802 100644 --- a/internal/api/middleware/audit.go +++ b/internal/api/middleware/audit.go @@ -221,11 +221,11 @@ func NewAuditServiceAdapter(recordFn func(ctx context.Context, actor string, act // RecordAPICall implements AuditRecorder by translating API call data into an audit event. func (a *AuditServiceAdapter) RecordAPICall(ctx context.Context, method, path, actor string, bodyHash string, status int, latencyMs int64) error { details := map[string]interface{}{ - "method": method, - "path": path, - "body_hash": bodyHash, - "status": status, - "latency_ms": latencyMs, + "method": method, + "path": path, + "body_hash": bodyHash, + "status": status, + "latency_ms": latencyMs, } action := fmt.Sprintf("api_%s", strings.ToLower(method)) diff --git a/internal/api/middleware/audit_test.go b/internal/api/middleware/audit_test.go index 8c7c742..35993a5 100644 --- a/internal/api/middleware/audit_test.go +++ b/internal/api/middleware/audit_test.go @@ -15,10 +15,10 @@ import ( // mockAuditRecorder captures RecordAPICall invocations for testing. type mockAuditRecorder struct { - mu sync.Mutex - calls []auditCall - err error // if non-nil, RecordAPICall returns this - block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning + mu sync.Mutex + calls []auditCall + err error // if non-nil, RecordAPICall returns this + block chan struct{} // if non-nil, RecordAPICall blocks on receive before returning } type auditCall struct { diff --git a/internal/api/middleware/cors_test.go b/internal/api/middleware/cors_test.go index 3a8768d..0fdae3a 100644 --- a/internal/api/middleware/cors_test.go +++ b/internal/api/middleware/cors_test.go @@ -40,9 +40,9 @@ func TestNewCORS_NilOriginsDeniesAll(t *testing.T) { // TestNewCORS_M013_ContractDocumentedInOrder pins the documented dispatch // order so a refactor cannot silently invert the cases: // -// 1. len(AllowedOrigins) == 0 → deny (no CORS headers) -// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *) -// 3. else → exact-match allowlist with Vary: Origin +// 1. len(AllowedOrigins) == 0 → deny (no CORS headers) +// 2. AllowedOrigins == ["*"] → allow all (Access-Control-Allow-Origin: *) +// 3. else → exact-match allowlist with Vary: Origin // // If a refactor accidentally falls through to the allow-all branch when // AllowedOrigins is empty, this test fails on case 1. @@ -293,9 +293,9 @@ func TestNewCORS_MultipleOrigins(t *testing.T) { })) tests := []struct { - origin string - shouldAllow bool - description string + origin string + shouldAllow bool + description string }{ {"https://app.example.com", true, "first origin in list"}, {"https://admin.example.com", true, "second origin in list"}, diff --git a/internal/api/middleware/middleware.go b/internal/api/middleware/middleware.go index 08e16b0..a1bdb34 100644 --- a/internal/api/middleware/middleware.go +++ b/internal/api/middleware/middleware.go @@ -290,11 +290,11 @@ func NewRateLimiter(cfg RateLimitConfig) func(http.Handler) http.Handler { } limiter := &keyedRateLimiter{ - ipRate: cfg.RPS, - ipBurst: float64(cfg.BurstSize), - userRate: perUserRPS, - userBurst: perUserBurst, - buckets: make(map[string]*tokenBucket), + ipRate: cfg.RPS, + ipBurst: float64(cfg.BurstSize), + userRate: perUserRPS, + userBurst: perUserBurst, + buckets: make(map[string]*tokenBucket), } return func(next http.Handler) http.Handler { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1e4caf7..f5eb553 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -990,9 +990,9 @@ func TestGetLogLevel_AllLevels(t *testing.T) { {"info", slog.LevelInfo}, {"warn", slog.LevelWarn}, {"error", slog.LevelError}, - {"unknown", slog.LevelInfo}, // default fallback - {"", slog.LevelInfo}, // empty string - {"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default + {"unknown", slog.LevelInfo}, // default fallback + {"", slog.LevelInfo}, // empty string + {"DEBUG", slog.LevelInfo}, // case-sensitive, no match → default } for _, tt := range tests { t.Run(tt.level, func(t *testing.T) { @@ -1091,6 +1091,7 @@ func TestGetEnvBool(t *testing.T) { }) } } + // I-003: Job timeout reaper configuration tests func TestConfig_Scheduler_JobTimeoutDefaults(t *testing.T) { clearCertctlEnv(t) diff --git a/internal/connector/discovery/awssm/awssm_test.go b/internal/connector/discovery/awssm/awssm_test.go index c735875..0665641 100644 --- a/internal/connector/discovery/awssm/awssm_test.go +++ b/internal/connector/discovery/awssm/awssm_test.go @@ -20,10 +20,10 @@ import ( // mockSMClient is a mock implementation of SMClient for testing. type mockSMClient struct { - secrets map[string]string // secret name -> secret value - secretMetadata map[string]SecretMetadata // secret name -> metadata - listError error - getErrors map[string]error // secret name -> error + secrets map[string]string // secret name -> secret value + secretMetadata map[string]SecretMetadata // secret name -> metadata + listError error + getErrors map[string]error // secret name -> error } func newMockSMClient() *mockSMClient { @@ -369,4 +369,3 @@ func TestSource_Discover_AgentIDAndSourcePath(t *testing.T) { t.Errorf("expected source path 'aws-sm://eu-west-1/my-secret', got %s", report.Certificates[0].SourcePath) } } - diff --git a/internal/connector/discovery/azurekv/azurekv.go b/internal/connector/discovery/azurekv/azurekv.go index 3d514bc..d651707 100644 --- a/internal/connector/discovery/azurekv/azurekv.go +++ b/internal/connector/discovery/azurekv/azurekv.go @@ -69,10 +69,10 @@ type certificateListResponse struct { Value []struct { ID string `json:"id"` Attributes struct { - Enabled int64 `json:"enabled"` - Created int64 `json:"created"` - Updated int64 `json:"updated"` - Exp int64 `json:"exp"` + Enabled int64 `json:"enabled"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` + Exp int64 `json:"exp"` } `json:"attributes,omitempty"` Tags map[string]string `json:"tags,omitempty"` } `json:"value"` @@ -84,10 +84,10 @@ type certificateBundle struct { ID string `json:"id"` CER string `json:"cer"` Attributes struct { - Enabled int64 `json:"enabled"` - Created int64 `json:"created"` - Updated int64 `json:"updated"` - Exp int64 `json:"exp"` + Enabled int64 `json:"enabled"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` + Exp int64 `json:"exp"` } `json:"attributes,omitempty"` } @@ -170,10 +170,10 @@ func (s *Source) Discover(ctx context.Context) (*domain.DiscoveryReport, error) s.logger.Info("starting Azure Key Vault discovery", "vault_url", s.config.VaultURL) report := &domain.DiscoveryReport{ - AgentID: "cloud-azure-kv", - Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)}, + AgentID: "cloud-azure-kv", + Directories: []string{fmt.Sprintf("azure-kv://%s/", s.config.VaultURL)}, Certificates: []domain.DiscoveredCertEntry{}, - Errors: []string{}, + Errors: []string{}, } startTime := time.Now() diff --git a/internal/connector/discovery/gcpsm/gcpsm_test.go b/internal/connector/discovery/gcpsm/gcpsm_test.go index f63a128..7e992d2 100644 --- a/internal/connector/discovery/gcpsm/gcpsm_test.go +++ b/internal/connector/discovery/gcpsm/gcpsm_test.go @@ -82,7 +82,7 @@ func generateTestCertificate(cn string, expire time.Duration) (*x509.Certificate ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, }, - DNSNames: []string{"example.com", "*.example.com"}, + DNSNames: []string{"example.com", "*.example.com"}, EmailAddresses: []string{"test@example.com"}, } diff --git a/internal/connector/issuer/acme/acme_test.go b/internal/connector/issuer/acme/acme_test.go index a518fbc..a2ba96e 100644 --- a/internal/connector/issuer/acme/acme_test.go +++ b/internal/connector/issuer/acme/acme_test.go @@ -798,9 +798,9 @@ func TestValidateConfig_DNSPersistIssuerDomainRequired(t *testing.T) { c := New(nil, testLogger()) cfg, _ := json.Marshal(map[string]string{ - "directory_url": srv.URL, - "email": "test@example.com", - "challenge_type": "dns-persist-01", + "directory_url": srv.URL, + "email": "test@example.com", + "challenge_type": "dns-persist-01", "dns_present_script": "/tmp/script.sh", // Missing dns_persist_issuer_domain }) @@ -870,9 +870,9 @@ func TestValidateConfig_DNS01WithPresentScript(t *testing.T) { c := New(nil, testLogger()) cfg, _ := json.Marshal(map[string]string{ - "directory_url": srv.URL, - "email": "test@example.com", - "challenge_type": "dns-01", + "directory_url": srv.URL, + "email": "test@example.com", + "challenge_type": "dns-01", "dns_present_script": "/bin/sh", "dns_cleanup_script": "/bin/sh", }) @@ -897,10 +897,10 @@ func TestValidateConfig_DNSPersist01WithAllFields(t *testing.T) { c := New(nil, testLogger()) cfg, _ := json.Marshal(map[string]string{ - "directory_url": srv.URL, - "email": "test@example.com", - "challenge_type": "dns-persist-01", - "dns_present_script": "/bin/sh", + "directory_url": srv.URL, + "email": "test@example.com", + "challenge_type": "dns-persist-01", + "dns_present_script": "/bin/sh", "dns_persist_issuer_domain": "letsencrypt.org", }) diff --git a/internal/connector/issuer/acme/ari_test.go b/internal/connector/issuer/acme/ari_test.go index 8fc1842..f6f2ca5 100644 --- a/internal/connector/issuer/acme/ari_test.go +++ b/internal/connector/issuer/acme/ari_test.go @@ -74,8 +74,8 @@ func TestGetRenewalInfo_NotFound(t *testing.T) { if r.URL.Path == "/directory" && r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ - "newOrder": "/acme/new-order", - "newAccount": "/acme/new-account", + "newOrder": "/acme/new-order", + "newAccount": "/acme/new-account", }) return } @@ -115,8 +115,8 @@ func TestGetRenewalInfo_ServerError(t *testing.T) { if r.URL.Path == "/directory" && r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ - "newOrder": "/acme/new-order", - "newAccount": "/acme/new-account", + "newOrder": "/acme/new-order", + "newAccount": "/acme/new-account", }) return } diff --git a/internal/connector/issuer/acme/pebble_mock_test.go b/internal/connector/issuer/acme/pebble_mock_test.go index 115942d..696178d 100644 --- a/internal/connector/issuer/acme/pebble_mock_test.go +++ b/internal/connector/issuer/acme/pebble_mock_test.go @@ -106,7 +106,7 @@ type pebbleMockServer struct { idSeq int64 // Behavior toggles for failure-mode tests. failNewAccount bool - rateLimitedOrder int32 // atomic counter; non-zero ⇒ first N orders return 429 + rateLimitedOrder int32 // atomic counter; non-zero ⇒ first N orders return 429 finalizeReturns string // "" (default), "processing-stuck", "invalid" authzPending bool // when true, new authzs start as "pending" and only flip to "valid" after the challenge endpoint is POSTed challengeType string // when set, the per-authz challenge type emitted (default "http-01") @@ -990,12 +990,12 @@ func TestPebbleMock_ContextCancel_DuringIssuance(t *testing.T) { // ───────────────────────────────────────────────────────────────────────────── type mockDNSSolver struct { - mu sync.Mutex - presented map[string]string // domain → keyAuth (or recordValue) - cleanedUp map[string]bool - presentErr error - cleanErr error - presentDelay time.Duration + mu sync.Mutex + presented map[string]string // domain → keyAuth (or recordValue) + cleanedUp map[string]bool + presentErr error + cleanErr error + presentDelay time.Duration } func newMockDNSSolver() *mockDNSSolver { diff --git a/internal/connector/issuer/acme/profile.go b/internal/connector/issuer/acme/profile.go index e15b823..1cdab8f 100644 --- a/internal/connector/issuer/acme/profile.go +++ b/internal/connector/issuer/acme/profile.go @@ -249,4 +249,3 @@ func signJWS(key *ecdsa.PrivateKey, kid, nonce, targetURL string, payload []byte return json.Marshal(jws) } - diff --git a/internal/connector/issuer/awsacmpca/awsacmpca.go b/internal/connector/issuer/awsacmpca/awsacmpca.go index f84abec..d5a1189 100644 --- a/internal/connector/issuer/awsacmpca/awsacmpca.go +++ b/internal/connector/issuer/awsacmpca/awsacmpca.go @@ -105,9 +105,9 @@ type GetCertificateOutput struct { // RevokeCertificateInput represents the request to revoke a certificate. type RevokeCertificateInput struct { - CAArn string - CertificateSerial string - RevocationReason string + CAArn string + CertificateSerial string + RevocationReason string } // GetCACertificateInput represents the request to retrieve the CA certificate. @@ -395,14 +395,14 @@ func mapRevocationReason(reason *string) string { } reasonMap := map[string]string{ - "unspecified": "UNSPECIFIED", - "keyCompromise": "KEY_COMPROMISE", - "caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE", - "affiliationChanged": "AFFILIATION_CHANGED", - "superseded": "SUPERSEDED", - "cessationOfOperation": "CESSATION_OF_OPERATION", - "certificateHold": "CERTIFICATE_HOLD", - "privilegeWithdrawn": "PRIVILEGE_WITHDRAWN", + "unspecified": "UNSPECIFIED", + "keyCompromise": "KEY_COMPROMISE", + "caCompromise": "CERTIFICATE_AUTHORITY_COMPROMISE", + "affiliationChanged": "AFFILIATION_CHANGED", + "superseded": "SUPERSEDED", + "cessationOfOperation": "CESSATION_OF_OPERATION", + "certificateHold": "CERTIFICATE_HOLD", + "privilegeWithdrawn": "PRIVILEGE_WITHDRAWN", } if mapped, ok := reasonMap[*reason]; ok { diff --git a/internal/connector/issuer/awsacmpca/awsacmpca_test.go b/internal/connector/issuer/awsacmpca/awsacmpca_test.go index 8ab88b6..9b887f4 100644 --- a/internal/connector/issuer/awsacmpca/awsacmpca_test.go +++ b/internal/connector/issuer/awsacmpca/awsacmpca_test.go @@ -22,15 +22,15 @@ import ( // mockACMPCAClient implements the ACMPCAClient interface for testing. type mockACMPCAClient struct { - issueCertificateErr error - getCertificateErr error - revokeCertificateErr error - getCACertificateErr error - issuedCertPEM string - issuedChainPEM string - caCertPEM string - caCertChainPEM string - lastIssueCertificateInput *awsacmpca.IssueCertificateInput + issueCertificateErr error + getCertificateErr error + revokeCertificateErr error + getCACertificateErr error + issuedCertPEM string + issuedChainPEM string + caCertPEM string + caCertChainPEM string + lastIssueCertificateInput *awsacmpca.IssueCertificateInput lastRevokeCertificateInput *awsacmpca.RevokeCertificateInput } diff --git a/internal/connector/issuer/digicert/digicert.go b/internal/connector/issuer/digicert/digicert.go index e25a178..b0e82b4 100644 --- a/internal/connector/issuer/digicert/digicert.go +++ b/internal/connector/issuer/digicert/digicert.go @@ -90,9 +90,9 @@ func New(config *Config, logger *slog.Logger) *Connector { // orderRequest is the JSON body for DigiCert certificate order submission. type orderRequest struct { - Certificate orderCert `json:"certificate"` - Organization orderOrg `json:"organization"` - ValidityYears int `json:"validity_years"` + Certificate orderCert `json:"certificate"` + Organization orderOrg `json:"organization"` + ValidityYears int `json:"validity_years"` } type orderCert struct { diff --git a/internal/connector/issuer/ejbca/ejbca.go b/internal/connector/issuer/ejbca/ejbca.go index 429c1a4..8567f9c 100644 --- a/internal/connector/issuer/ejbca/ejbca.go +++ b/internal/connector/issuer/ejbca/ejbca.go @@ -162,7 +162,7 @@ func (c *Connector) IssueCertificate(ctx context.Context, request issuer.Issuanc csrBase64 := base64.StdEncoding.EncodeToString(csrBlock.Bytes) enrollReq := map[string]interface{}{ - "certificate_request": csrBase64, + "certificate_request": csrBase64, "certificate_authority_name": c.config.CAName, } diff --git a/internal/connector/issuer/ejbca/ejbca_test.go b/internal/connector/issuer/ejbca/ejbca_test.go index d2954f7..1842e58 100644 --- a/internal/connector/issuer/ejbca/ejbca_test.go +++ b/internal/connector/issuer/ejbca/ejbca_test.go @@ -175,7 +175,7 @@ func TestEJBCAConnector(t *testing.T) { w.Header().Set("Content-Type", "application/json") respData := map[string]interface{}{ - "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), + "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate_chain": []string{base64.StdEncoding.EncodeToString(chainBlock.Bytes)}, "serial_number": "123456", } @@ -242,7 +242,7 @@ func TestEJBCAConnector(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") respData := map[string]interface{}{ - "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), + "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate_chain": []string{}, "serial_number": "789012", } @@ -314,7 +314,7 @@ func TestEJBCAConnector(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") respData := map[string]interface{}{ - "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), + "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate_chain": []string{}, "serial_number": "123456", } @@ -356,7 +356,7 @@ func TestEJBCAConnector(t *testing.T) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") respData := map[string]interface{}{ - "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), + "certificate": base64.StdEncoding.EncodeToString(certBlock.Bytes), "certificate_chain": []string{}, "serial_number": "654321", } diff --git a/internal/connector/issuer/entrust/entrust.go b/internal/connector/issuer/entrust/entrust.go index 621de58..00b276f 100644 --- a/internal/connector/issuer/entrust/entrust.go +++ b/internal/connector/issuer/entrust/entrust.go @@ -89,9 +89,9 @@ func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client) // enrollmentRequest is the JSON body for Entrust enrollment submission. type enrollmentRequest struct { - CSR string `json:"csr"` - ProfileId string `json:"profileId,omitempty"` - SubjectAltNames []san `json:"subjectAltNames,omitempty"` + CSR string `json:"csr"` + ProfileId string `json:"profileId,omitempty"` + SubjectAltNames []san `json:"subjectAltNames,omitempty"` CertificateAuthority string `json:"certificateAuthority,omitempty"` } diff --git a/internal/connector/issuer/globalsign/globalsign.go b/internal/connector/issuer/globalsign/globalsign.go index ae0b913..d3e007f 100644 --- a/internal/connector/issuer/globalsign/globalsign.go +++ b/internal/connector/issuer/globalsign/globalsign.go @@ -107,9 +107,9 @@ func NewWithHTTPClient(config *Config, logger *slog.Logger, client *http.Client) // certificateRequest is the JSON body for GlobalSign certificate order submission. type certificateRequest struct { - CSR string `json:"csr"` + CSR string `json:"csr"` SubjectDN subjectDNRequest `json:"subject_dn"` - SAN sanRequest `json:"san,omitempty"` + SAN sanRequest `json:"san,omitempty"` } type subjectDNRequest struct { diff --git a/internal/connector/issuer/googlecas/googlecas.go b/internal/connector/issuer/googlecas/googlecas.go index 341b263..abd53d5 100644 --- a/internal/connector/issuer/googlecas/googlecas.go +++ b/internal/connector/issuer/googlecas/googlecas.go @@ -93,10 +93,10 @@ type Connector struct { httpClient *http.Client // OAuth2 token caching - mu sync.Mutex - tokenCache *cachedToken - saKey *serviceAccountKey - rsaKey *rsa.PrivateKey + mu sync.Mutex + tokenCache *cachedToken + saKey *serviceAccountKey + rsaKey *rsa.PrivateKey } // New creates a new Google CAS connector with the given configuration and logger. diff --git a/internal/connector/issuer/openssl/openssl.go b/internal/connector/issuer/openssl/openssl.go index 10d118d..52413c5 100644 --- a/internal/connector/issuer/openssl/openssl.go +++ b/internal/connector/issuer/openssl/openssl.go @@ -479,9 +479,9 @@ func (c *Connector) parseCertificate(certPEM []byte) (*x509.Certificate, string, // Format: [{"serial": "...", "revoked_at": "...", "reason_code": ...}, ...] func (c *Connector) marshalRevokedSerials(revokedCerts []issuer.RevokedCertEntry) ([]byte, error) { type RevokedEntry struct { - Serial string `json:"serial"` - RevokedAt string `json:"revoked_at"` - ReasonCode int `json:"reason_code"` + Serial string `json:"serial"` + RevokedAt string `json:"revoked_at"` + ReasonCode int `json:"reason_code"` } entries := make([]RevokedEntry, len(revokedCerts)) diff --git a/internal/connector/issuer/openssl/openssl_test.go b/internal/connector/issuer/openssl/openssl_test.go index adc5711..aefbb6b 100644 --- a/internal/connector/issuer/openssl/openssl_test.go +++ b/internal/connector/issuer/openssl/openssl_test.go @@ -643,7 +643,7 @@ func generateTestCSR(cn string) (*x509.CertificateRequest, string, error) { } csrTemplate := x509.CertificateRequest{ - Subject: subject, + Subject: subject, DNSNames: []string{cn, "www." + cn}, } diff --git a/internal/connector/issuer/stepca/stepca.go b/internal/connector/issuer/stepca/stepca.go index 05829a4..3faf858 100644 --- a/internal/connector/issuer/stepca/stepca.go +++ b/internal/connector/issuer/stepca/stepca.go @@ -168,9 +168,9 @@ type signRequest struct { // signResponse is the JSON response from the step-ca /sign endpoint. type signResponse struct { - ServerPEM certificateChain `json:"serverPEM,omitempty"` - CaPEM certificateChain `json:"caPEM,omitempty"` - CertChainPEM []certBlock `json:"certChainPEM,omitempty"` + ServerPEM certificateChain `json:"serverPEM,omitempty"` + CaPEM certificateChain `json:"caPEM,omitempty"` + CertChainPEM []certBlock `json:"certChainPEM,omitempty"` } type certificateChain struct { @@ -380,14 +380,14 @@ func (c *Connector) generateProvisionerToken(subject string, sans []string) (str // step-ca expects: aud = /1.0/sign (the sign endpoint audience) claims := map[string]interface{}{ - "sub": subject, - "iss": c.config.ProvisionerName, - "aud": c.config.CAURL + "/1.0/sign", - "nbf": now.Unix(), - "iat": now.Unix(), - "exp": now.Add(5 * time.Minute).Unix(), - "jti": generateJTI(), - "sha": kid, // step-ca uses this to look up the provisioner by key fingerprint + "sub": subject, + "iss": c.config.ProvisionerName, + "aud": c.config.CAURL + "/1.0/sign", + "nbf": now.Unix(), + "iat": now.Unix(), + "exp": now.Add(5 * time.Minute).Unix(), + "jti": generateJTI(), + "sha": kid, // step-ca uses this to look up the provisioner by key fingerprint } if len(sans) > 0 { diff --git a/internal/connector/issuer/stepca/stepca_test.go b/internal/connector/issuer/stepca/stepca_test.go index 8a45434..852fd7e 100644 --- a/internal/connector/issuer/stepca/stepca_test.go +++ b/internal/connector/issuer/stepca/stepca_test.go @@ -1574,9 +1574,9 @@ func TestLoadProvisionerKey_FileNotReadable(t *testing.T) { // Test with a provisioner key path that can't be read config := stepca.Config{ - CAURL: srv.URL, - ProvisionerName: "test-provisioner", - ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist + CAURL: srv.URL, + ProvisionerName: "test-provisioner", + ProvisionerKeyPath: "/root/.ssh/no_such_key", // Permission denied or doesn't exist ProvisionerPassword: "password", } @@ -1770,4 +1770,3 @@ func TestIntegration_FullLifecycle(t *testing.T) { t.Errorf("Expected status 'completed', got '%s'", status.Status) } } - diff --git a/internal/connector/issuer/vault/vault.go b/internal/connector/issuer/vault/vault.go index ba38c07..7b0de6d 100644 --- a/internal/connector/issuer/vault/vault.go +++ b/internal/connector/issuer/vault/vault.go @@ -86,9 +86,9 @@ func New(config *Config, logger *slog.Logger) *Connector { // vaultResponse is the standard Vault API response wrapper. type vaultResponse struct { - Data json.RawMessage `json:"data"` - Errors []string `json:"errors,omitempty"` - Warnings []string `json:"warnings,omitempty"` + Data json.RawMessage `json:"data"` + Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` } // signData holds the data returned from the /sign endpoint. diff --git a/internal/connector/notifier/email/email_test.go b/internal/connector/notifier/email/email_test.go index ddf2b0b..dda0432 100644 --- a/internal/connector/notifier/email/email_test.go +++ b/internal/connector/notifier/email/email_test.go @@ -88,11 +88,11 @@ func TestEmail_ValidateConfig_MissingPort(t *testing.T) { func TestEmail_ValidateConfig_MissingFromAddress(t *testing.T) { cfg := &Config{ - SMTPHost: "smtp.example.com", - SMTPPort: 587, - Username: "user", - Password: "pass", - UseTLS: true, + SMTPHost: "smtp.example.com", + SMTPPort: 587, + Username: "user", + Password: "pass", + UseTLS: true, } rawConfig, _ := json.Marshal(cfg) diff --git a/internal/connector/target/envoy/envoy.go b/internal/connector/target/envoy/envoy.go index f90fccb..ebcd24f 100644 --- a/internal/connector/target/envoy/envoy.go +++ b/internal/connector/target/envoy/envoy.go @@ -19,11 +19,11 @@ import ( // to a directory that Envoy watches via its SDS (Secret Discovery Service) // file-based configuration or static filename references in the bootstrap config. type Config struct { - CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required) - CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem) - KeyFilename string `json:"key_filename"` // Filename for private key (default: key.pem) - ChainFilename string `json:"chain_filename"` // Optional filename for chain (if set, chain written separately) - SDSConfig bool `json:"sds_config"` // If true, write an SDS discovery JSON file for file-based SDS + CertDir string `json:"cert_dir"` // Directory where Envoy watches for cert files (required) + CertFilename string `json:"cert_filename"` // Filename for certificate (default: cert.pem) + KeyFilename string `json:"key_filename"` // Filename for private key (default: key.pem) + ChainFilename string `json:"chain_filename"` // Optional filename for chain (if set, chain written separately) + SDSConfig bool `json:"sds_config"` // If true, write an SDS discovery JSON file for file-based SDS } // SDSResource represents an Envoy SDS tls_certificate resource for file-based SDS. @@ -34,9 +34,9 @@ type SDSResource struct { // SDSTLSCertificate represents a single SDS tls_certificate entry. type SDSTLSCertificate struct { - Type string `json:"@type"` - Name string `json:"name"` - TLSCertificate TLSCertificate `json:"tls_certificate"` + Type string `json:"@type"` + Name string `json:"name"` + TLSCertificate TLSCertificate `json:"tls_certificate"` } // TLSCertificate contains the file paths for cert and key in Envoy's SDS format. diff --git a/internal/connector/target/f5/f5.go b/internal/connector/target/f5/f5.go index 7acd8f6..e23f62f 100644 --- a/internal/connector/target/f5/f5.go +++ b/internal/connector/target/f5/f5.go @@ -457,13 +457,13 @@ func (c *Connector) DeployCertificate(ctx context.Context, request target.Deploy Message: "Certificate uploaded and SSL profile updated via iControl REST", DeployedAt: time.Now(), Metadata: map[string]string{ - "host": c.config.Host, - "partition": c.config.Partition, - "ssl_profile": c.config.SSLProfile, - "cert_object_name": certName, - "key_object_name": keyName, + "host": c.config.Host, + "partition": c.config.Partition, + "ssl_profile": c.config.SSLProfile, + "cert_object_name": certName, + "key_object_name": keyName, "chain_object_name": chainName, - "duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()), + "duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()), }, }, nil } @@ -561,12 +561,12 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid Message: fmt.Sprintf("SSL profile %q has cert %q configured", c.config.SSLProfile, profile.Cert), ValidatedAt: time.Now(), Metadata: map[string]string{ - "host": c.config.Host, - "ssl_profile": c.config.SSLProfile, - "current_cert": profile.Cert, - "current_key": profile.Key, + "host": c.config.Host, + "ssl_profile": c.config.SSLProfile, + "current_cert": profile.Cert, + "current_key": profile.Key, "current_chain": profile.Chain, - "duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()), + "duration_ms": fmt.Sprintf("%d", validationDuration.Milliseconds()), }, }, nil } diff --git a/internal/connector/target/f5/f5_test.go b/internal/connector/target/f5/f5_test.go index dc1a5c3..cf925df 100644 --- a/internal/connector/target/f5/f5_test.go +++ b/internal/connector/target/f5/f5_test.go @@ -25,14 +25,14 @@ type mockF5Client struct { calls []mockCall // Configurable responses per method - authenticateErr error - authenticateCount int // tracks number of Authenticate calls - uploadFileErr error - uploadFileErrOn string // only error when filename contains this substring - installCertErr error - installCertErrOn string - installKeyErr error - createTransactionID string + authenticateErr error + authenticateCount int // tracks number of Authenticate calls + uploadFileErr error + uploadFileErrOn string // only error when filename contains this substring + installCertErr error + installCertErrOn string + installKeyErr error + createTransactionID string createTransactionErr error commitTransactionErr error updateSSLProfileErr error diff --git a/internal/connector/target/iis/winrm.go b/internal/connector/target/iis/winrm.go index 2712f2d..73345fc 100644 --- a/internal/connector/target/iis/winrm.go +++ b/internal/connector/target/iis/winrm.go @@ -59,9 +59,9 @@ func newWinRMExecutor(cfg *WinRMConfig) (*winrmExecutor, error) { port, cfg.UseHTTPS, cfg.Insecure, - nil, // CA cert - nil, // Client cert - nil, // Client key + nil, // CA cert + nil, // Client cert + nil, // Client key timeout, ) diff --git a/internal/connector/target/javakeystore/javakeystore.go b/internal/connector/target/javakeystore/javakeystore.go index 84140cb..af8d6f5 100644 --- a/internal/connector/target/javakeystore/javakeystore.go +++ b/internal/connector/target/javakeystore/javakeystore.go @@ -263,10 +263,10 @@ func (c *Connector) DeployCertificate(ctx context.Context, request target.Deploy Message: fmt.Sprintf("Certificate imported to %s (alias: %s, thumbprint: %s)", c.config.KeystorePath, c.config.Alias, thumbprint), DeployedAt: time.Now(), Metadata: map[string]string{ - "thumbprint": thumbprint, - "alias": c.config.Alias, - "keystore_type": c.config.KeystoreType, - "keystore_path": c.config.KeystorePath, + "thumbprint": thumbprint, + "alias": c.config.Alias, + "keystore_type": c.config.KeystoreType, + "keystore_path": c.config.KeystorePath, }, }, nil } diff --git a/internal/connector/target/javakeystore/javakeystore_test.go b/internal/connector/target/javakeystore/javakeystore_test.go index 344d081..73dfb5d 100644 --- a/internal/connector/target/javakeystore/javakeystore_test.go +++ b/internal/connector/target/javakeystore/javakeystore_test.go @@ -240,7 +240,7 @@ func TestDeployCertificate_Success(t *testing.T) { mock := &mockExecutor{ responses: []mockResponse{ - {Output: "", Err: nil}, // keytool -delete (alias may not exist) + {Output: "", Err: nil}, // keytool -delete (alias may not exist) {Output: "Import command completed", Err: nil}, // keytool -importkeystore }, } @@ -355,8 +355,8 @@ func TestDeployCertificate_WithReload(t *testing.T) { mock := &mockExecutor{ responses: []mockResponse{ // No existing keystore → delete skipped → import is call 0, reload is call 1 - {Output: "Imported", Err: nil}, // import - {Output: "restarted", Err: nil}, // reload + {Output: "Imported", Err: nil}, // import + {Output: "restarted", Err: nil}, // reload }, } c := NewWithExecutor(&Config{ @@ -391,8 +391,8 @@ func TestDeployCertificate_ReloadFailed_NonFatal(t *testing.T) { mock := &mockExecutor{ responses: []mockResponse{ - {Output: "", Err: nil}, // delete - {Output: "Imported", Err: nil}, // import + {Output: "", Err: nil}, // delete + {Output: "Imported", Err: nil}, // import {Output: "Failed to restart", Err: fmt.Errorf("exit 1")}, // reload fails }, } diff --git a/internal/connector/target/k8ssecret/k8ssecret.go b/internal/connector/target/k8ssecret/k8ssecret.go index c5cdbc3..8b810a7 100644 --- a/internal/connector/target/k8ssecret/k8ssecret.go +++ b/internal/connector/target/k8ssecret/k8ssecret.go @@ -21,9 +21,9 @@ import ( // Supports in-cluster auth by default (ServiceAccount token auto-mounted) or // out-of-cluster auth via kubeconfig file. type Config struct { - Namespace string `json:"namespace"` // Required. Kubernetes namespace. - SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret. - Labels map[string]string `json:"labels,omitempty"` // Optional. Additional labels to add to the Secret. + Namespace string `json:"namespace"` // Required. Kubernetes namespace. + SecretName string `json:"secret_name"` // Required. Name of the kubernetes.io/tls Secret. + Labels map[string]string `json:"labels,omitempty"` // Optional. Additional labels to add to the Secret. KubeconfigPath string `json:"kubeconfig_path,omitempty"` // Optional. Path to kubeconfig for out-of-cluster auth. } diff --git a/internal/connector/target/k8ssecret/k8ssecret_test.go b/internal/connector/target/k8ssecret/k8ssecret_test.go index 81b160d..59cb54c 100644 --- a/internal/connector/target/k8ssecret/k8ssecret_test.go +++ b/internal/connector/target/k8ssecret/k8ssecret_test.go @@ -93,7 +93,7 @@ func (m *mockK8sClient) DeleteSecret(ctx context.Context, namespace, name string func TestValidateConfig_Success_MinimalConfig(t *testing.T) { cfg := map[string]interface{}{ - "namespace": "default", + "namespace": "default", "secret_name": "my-cert", } @@ -644,4 +644,3 @@ func contains(s, substr string) bool { } return false } - diff --git a/internal/connector/target/ssh/ssh.go b/internal/connector/target/ssh/ssh.go index 105ff04..6239fdf 100644 --- a/internal/connector/target/ssh/ssh.go +++ b/internal/connector/target/ssh/ssh.go @@ -411,9 +411,9 @@ func (c *realSSHClient) Connect(ctx context.Context) error { } sshConfig := &ssh.ClientConfig{ - User: c.config.User, - Auth: authMethods, - Timeout: time.Duration(c.config.Timeout) * time.Second, + User: c.config.User, + Auth: authMethods, + Timeout: time.Duration(c.config.Timeout) * time.Second, // InsecureIgnoreHostKey is used intentionally: certctl deploys to known // infrastructure (the operator explicitly configures each target host). // This is the same security rationale as network scanner's InsecureSkipVerify diff --git a/internal/connector/target/ssh/ssh_server_fixture_test.go b/internal/connector/target/ssh/ssh_server_fixture_test.go index 033c461..e8e1bf0 100644 --- a/internal/connector/target/ssh/ssh_server_fixture_test.go +++ b/internal/connector/target/ssh/ssh_server_fixture_test.go @@ -42,15 +42,15 @@ type fakeSSHServer struct { user string password string - wg sync.WaitGroup - mu sync.Mutex - closed bool + wg sync.WaitGroup + mu sync.Mutex + closed bool // Optional behaviour toggles for failure-mode tests. - rejectAuth bool // reject all auth attempts (auth failure path) - dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure) - failExec bool // exec sessions return non-zero exit (Execute error path) - failSFTP bool // refuse sftp subsystem (SFTP failure path) + rejectAuth bool // reject all auth attempts (auth failure path) + dropOnHandshake bool // close conn before SSH NewServerConn returns (handshake failure) + failExec bool // exec sessions return non-zero exit (Execute error path) + failSFTP bool // refuse sftp subsystem (SFTP failure path) } // startFakeSSHServer binds a fresh server on a random local port and returns diff --git a/internal/connector/target/wincertstore/wincertstore.go b/internal/connector/target/wincertstore/wincertstore.go index c4203a9..4879f8d 100644 --- a/internal/connector/target/wincertstore/wincertstore.go +++ b/internal/connector/target/wincertstore/wincertstore.go @@ -310,4 +310,3 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid // Ensure Connector implements target.Connector. var _ target.Connector = (*Connector)(nil) - diff --git a/internal/connector/target/wincertstore/wincertstore_test.go b/internal/connector/target/wincertstore/wincertstore_test.go index d5e4e90..e8b7e3b 100644 --- a/internal/connector/target/wincertstore/wincertstore_test.go +++ b/internal/connector/target/wincertstore/wincertstore_test.go @@ -26,10 +26,10 @@ func testLogger() *slog.Logger { // mockExecutor records PowerShell scripts and returns configurable responses. type mockExecutor struct { - scripts []string - responses []string - errors []error - callIndex int + scripts []string + responses []string + errors []error + callIndex int } func (m *mockExecutor) Execute(ctx context.Context, script string) (string, error) { diff --git a/internal/domain/certificate_test.go b/internal/domain/certificate_test.go index 182985d..f2a67c0 100644 --- a/internal/domain/certificate_test.go +++ b/internal/domain/certificate_test.go @@ -4,14 +4,14 @@ import "testing" func TestCertificateStatus_Constants(t *testing.T) { tests := map[string]CertificateStatus{ - "Pending": CertificateStatusPending, - "Active": CertificateStatusActive, - "Expiring": CertificateStatusExpiring, - "Expired": CertificateStatusExpired, - "RenewalInProgress": CertificateStatusRenewalInProgress, - "Failed": CertificateStatusFailed, - "Revoked": CertificateStatusRevoked, - "Archived": CertificateStatusArchived, + "Pending": CertificateStatusPending, + "Active": CertificateStatusActive, + "Expiring": CertificateStatusExpiring, + "Expired": CertificateStatusExpired, + "RenewalInProgress": CertificateStatusRenewalInProgress, + "Failed": CertificateStatusFailed, + "Revoked": CertificateStatusRevoked, + "Archived": CertificateStatusArchived, } for expected, got := range tests { if string(got) != expected { diff --git a/internal/domain/connector.go b/internal/domain/connector.go index d75c897..7dfb37a 100644 --- a/internal/domain/connector.go +++ b/internal/domain/connector.go @@ -11,7 +11,7 @@ type Issuer struct { Name string `json:"name"` Type IssuerType `json:"type"` Config json.RawMessage `json:"config"` - EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API) + EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API) Enabled bool `json:"enabled"` LastTestedAt *time.Time `json:"last_tested_at,omitempty"` TestStatus string `json:"test_status,omitempty"` @@ -27,13 +27,13 @@ type DeploymentTarget struct { Type TargetType `json:"type"` AgentID string `json:"agent_id"` Config json.RawMessage `json:"config"` - EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API) + EncryptedConfig []byte `json:"-"` // AES-GCM encrypted full config (never exposed via API) Enabled bool `json:"enabled"` LastTestedAt *time.Time `json:"last_tested_at,omitempty"` TestStatus string `json:"test_status,omitempty"` Source string `json:"source,omitempty"` - RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active) - RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement + RetiredAt *time.Time `json:"retired_at,omitempty"` // I-004: soft-retirement timestamp (nil = active) + RetiredReason *string `json:"retired_reason,omitempty"` // I-004: reason captured at cascade retirement CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } @@ -65,11 +65,11 @@ type Agent struct { // docs/architecture.md ER diagram (which documents DB shape, not API // shape) and coverage-gap-audit-2026-04-24-v5/unified-audit.md // cat-s5-apikey_leak for the full closure rationale. - APIKeyHash string `json:"-"` - OS string `json:"os"` - Architecture string `json:"architecture"` - IPAddress string `json:"ip_address"` - Version string `json:"version"` + APIKeyHash string `json:"-"` + OS string `json:"os"` + Architecture string `json:"architecture"` + IPAddress string `json:"ip_address"` + Version string `json:"version"` // I-004: soft-retirement fields. An agent with RetiredAt != nil is the // canonical "retired" state. The Status column remains as before (Online // / Offline / Degraded) and is preserved at retirement time as the @@ -115,9 +115,9 @@ func (a *Agent) IsRetired() bool { return a != nil && a.RetiredAt != nil } // any non-zero count blocks a default retire with HTTP 409 and requires an // explicit ?force=true&reason=... escape hatch from the operator. type AgentDependencyCounts struct { - ActiveTargets int `json:"active_targets"` // deployment_targets.agent_id=id AND retired_at IS NULL + ActiveTargets int `json:"active_targets"` // deployment_targets.agent_id=id AND retired_at IS NULL ActiveCertificates int `json:"active_certificates"` // certificates currently deployed via one of this agent's active targets - PendingJobs int `json:"pending_jobs"` // jobs.agent_id=id AND status IN (Pending, AwaitingCSR, AwaitingApproval, Running) + PendingJobs int `json:"pending_jobs"` // jobs.agent_id=id AND status IN (Pending, AwaitingCSR, AwaitingApproval, Running) } // HasDependencies reports whether any preflight counter is non-zero. @@ -180,14 +180,14 @@ const ( type IssuerType string const ( - IssuerTypeACME IssuerType = "ACME" - IssuerTypeGenericCA IssuerType = "GenericCA" - IssuerTypeStepCA IssuerType = "StepCA" - IssuerTypeOpenSSL IssuerType = "OpenSSL" - IssuerTypeVault IssuerType = "VaultPKI" - IssuerTypeDigiCert IssuerType = "DigiCert" - IssuerTypeSectigo IssuerType = "Sectigo" - IssuerTypeGoogleCAS IssuerType = "GoogleCAS" + IssuerTypeACME IssuerType = "ACME" + IssuerTypeGenericCA IssuerType = "GenericCA" + IssuerTypeStepCA IssuerType = "StepCA" + IssuerTypeOpenSSL IssuerType = "OpenSSL" + IssuerTypeVault IssuerType = "VaultPKI" + IssuerTypeDigiCert IssuerType = "DigiCert" + IssuerTypeSectigo IssuerType = "Sectigo" + IssuerTypeGoogleCAS IssuerType = "GoogleCAS" IssuerTypeAWSACMPCA IssuerType = "AWSACMPCA" IssuerTypeEntrust IssuerType = "Entrust" IssuerTypeGlobalSign IssuerType = "GlobalSign" @@ -198,16 +198,16 @@ const ( type TargetType string const ( - TargetTypeNGINX TargetType = "NGINX" - TargetTypeApache TargetType = "Apache" - TargetTypeHAProxy TargetType = "HAProxy" - TargetTypeF5 TargetType = "F5" - TargetTypeIIS TargetType = "IIS" - TargetTypeTraefik TargetType = "Traefik" - TargetTypeCaddy TargetType = "Caddy" - TargetTypeEnvoy TargetType = "Envoy" - TargetTypePostfix TargetType = "Postfix" - TargetTypeDovecot TargetType = "Dovecot" + TargetTypeNGINX TargetType = "NGINX" + TargetTypeApache TargetType = "Apache" + TargetTypeHAProxy TargetType = "HAProxy" + TargetTypeF5 TargetType = "F5" + TargetTypeIIS TargetType = "IIS" + TargetTypeTraefik TargetType = "Traefik" + TargetTypeCaddy TargetType = "Caddy" + TargetTypeEnvoy TargetType = "Envoy" + TargetTypePostfix TargetType = "Postfix" + TargetTypeDovecot TargetType = "Dovecot" TargetTypeSSH TargetType = "SSH" TargetTypeWinCertStore TargetType = "WinCertStore" TargetTypeJavaKeystore TargetType = "JavaKeystore" diff --git a/internal/domain/health_check.go b/internal/domain/health_check.go index f8cc17d..1805794 100644 --- a/internal/domain/health_check.go +++ b/internal/domain/health_check.go @@ -24,34 +24,34 @@ func IsValidHealthStatus(s string) bool { // EndpointHealthCheck represents a monitored TLS endpoint. type EndpointHealthCheck struct { - ID string `json:"id"` - Endpoint string `json:"endpoint"` - CertificateID *string `json:"certificate_id,omitempty"` - NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"` - ExpectedFingerprint string `json:"expected_fingerprint"` - ObservedFingerprint string `json:"observed_fingerprint"` - Status HealthStatus `json:"status"` - ConsecutiveFailures int `json:"consecutive_failures"` - ResponseTimeMs int `json:"response_time_ms"` - TLSVersion string `json:"tls_version"` - CipherSuite string `json:"cipher_suite"` - CertSubject string `json:"cert_subject"` - CertIssuer string `json:"cert_issuer"` - CertExpiry *time.Time `json:"cert_expiry,omitempty"` - LastCheckedAt *time.Time `json:"last_checked_at,omitempty"` - LastSuccessAt *time.Time `json:"last_success_at,omitempty"` - LastFailureAt *time.Time `json:"last_failure_at,omitempty"` - LastTransitionAt *time.Time `json:"last_transition_at,omitempty"` - FailureReason string `json:"failure_reason"` - DegradedThreshold int `json:"degraded_threshold"` - DownThreshold int `json:"down_threshold"` - CheckIntervalSecs int `json:"check_interval_seconds"` - Enabled bool `json:"enabled"` - Acknowledged bool `json:"acknowledged"` - AcknowledgedBy string `json:"acknowledged_by,omitempty"` - AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID string `json:"id"` + Endpoint string `json:"endpoint"` + CertificateID *string `json:"certificate_id,omitempty"` + NetworkScanTargetID *string `json:"network_scan_target_id,omitempty"` + ExpectedFingerprint string `json:"expected_fingerprint"` + ObservedFingerprint string `json:"observed_fingerprint"` + Status HealthStatus `json:"status"` + ConsecutiveFailures int `json:"consecutive_failures"` + ResponseTimeMs int `json:"response_time_ms"` + TLSVersion string `json:"tls_version"` + CipherSuite string `json:"cipher_suite"` + CertSubject string `json:"cert_subject"` + CertIssuer string `json:"cert_issuer"` + CertExpiry *time.Time `json:"cert_expiry,omitempty"` + LastCheckedAt *time.Time `json:"last_checked_at,omitempty"` + LastSuccessAt *time.Time `json:"last_success_at,omitempty"` + LastFailureAt *time.Time `json:"last_failure_at,omitempty"` + LastTransitionAt *time.Time `json:"last_transition_at,omitempty"` + FailureReason string `json:"failure_reason"` + DegradedThreshold int `json:"degraded_threshold"` + DownThreshold int `json:"down_threshold"` + CheckIntervalSecs int `json:"check_interval_seconds"` + Enabled bool `json:"enabled"` + Acknowledged bool `json:"acknowledged"` + AcknowledgedBy string `json:"acknowledged_by,omitempty"` + AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // TransitionStatus computes the new health status based on the probe result. @@ -89,13 +89,13 @@ func (h *EndpointHealthCheck) TransitionStatus(probeSuccess bool, observedFinger // HealthHistoryEntry represents a single probe record. type HealthHistoryEntry struct { - ID string `json:"id"` - HealthCheckID string `json:"health_check_id"` - Status string `json:"status"` - ResponseTimeMs int `json:"response_time_ms"` - Fingerprint string `json:"fingerprint"` - FailureReason string `json:"failure_reason"` - CheckedAt time.Time `json:"checked_at"` + ID string `json:"id"` + HealthCheckID string `json:"health_check_id"` + Status string `json:"status"` + ResponseTimeMs int `json:"response_time_ms"` + Fingerprint string `json:"fingerprint"` + FailureReason string `json:"failure_reason"` + CheckedAt time.Time `json:"checked_at"` } // HealthCheckSummary contains aggregate counts by status. diff --git a/internal/domain/job.go b/internal/domain/job.go index 95cdd89..302c13a 100644 --- a/internal/domain/job.go +++ b/internal/domain/job.go @@ -7,23 +7,23 @@ import ( // Job represents a unit of work in the certificate control plane. type Job struct { - ID string `json:"id"` - Type JobType `json:"type"` - CertificateID string `json:"certificate_id"` - TargetID *string `json:"target_id,omitempty"` - AgentID *string `json:"agent_id,omitempty"` - Status JobStatus `json:"status"` - Attempts int `json:"attempts"` - MaxAttempts int `json:"max_attempts"` - LastError *string `json:"last_error,omitempty"` - ScheduledAt time.Time `json:"scheduled_at"` - StartedAt *time.Time `json:"started_at,omitempty"` - CompletedAt *time.Time `json:"completed_at,omitempty"` - CreatedAt time.Time `json:"created_at"` - VerificationStatus VerificationStatus `json:"verification_status"` - VerifiedAt *time.Time `json:"verified_at,omitempty"` - VerificationError *string `json:"verification_error,omitempty"` - VerificationFp *string `json:"verification_fingerprint,omitempty"` + ID string `json:"id"` + Type JobType `json:"type"` + CertificateID string `json:"certificate_id"` + TargetID *string `json:"target_id,omitempty"` + AgentID *string `json:"agent_id,omitempty"` + Status JobStatus `json:"status"` + Attempts int `json:"attempts"` + MaxAttempts int `json:"max_attempts"` + LastError *string `json:"last_error,omitempty"` + ScheduledAt time.Time `json:"scheduled_at"` + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + VerificationStatus VerificationStatus `json:"verification_status"` + VerifiedAt *time.Time `json:"verified_at,omitempty"` + VerificationError *string `json:"verification_error,omitempty"` + VerificationFp *string `json:"verification_fingerprint,omitempty"` } // JobType represents the classification of work to be performed. diff --git a/internal/domain/notification_test.go b/internal/domain/notification_test.go index 5e1c840..69d87a3 100644 --- a/internal/domain/notification_test.go +++ b/internal/domain/notification_test.go @@ -104,15 +104,15 @@ func TestNotificationEvent_RetryFields(t *testing.T) { next := time.Now().Add(2 * time.Minute) lastErr := "connection refused" event := &NotificationEvent{ - ID: "notif-retry-001", - Type: NotificationTypeExpirationWarning, - Channel: NotificationChannelWebhook, - Recipient: "https://hooks.example.com/certs", - Message: "retry me", - Status: string(NotificationStatusFailed), - RetryCount: 3, - NextRetryAt: &next, - LastError: &lastErr, + ID: "notif-retry-001", + Type: NotificationTypeExpirationWarning, + Channel: NotificationChannelWebhook, + Recipient: "https://hooks.example.com/certs", + Message: "retry me", + Status: string(NotificationStatusFailed), + RetryCount: 3, + NextRetryAt: &next, + LastError: &lastErr, } if event.RetryCount != 3 { diff --git a/internal/domain/ocsp_response_cache.go b/internal/domain/ocsp_response_cache.go index c05a6d2..34f3066 100644 --- a/internal/domain/ocsp_response_cache.go +++ b/internal/domain/ocsp_response_cache.go @@ -12,10 +12,10 @@ import "time" type OCSPResponseCacheEntry struct { IssuerID string `json:"issuer_id"` SerialHex string `json:"serial_hex"` - ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean - CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown" - RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked" - RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked" + ResponseDER []byte `json:"-"` // raw DER, omitted from admin JSON to keep responses lean + CertStatus string `json:"cert_status"` // "good" | "revoked" | "unknown" + RevocationReason int `json:"revocation_reason,omitempty"` // only set when CertStatus == "revoked" + RevokedAt time.Time `json:"revoked_at,omitempty"` // only set when CertStatus == "revoked" ThisUpdate time.Time `json:"this_update"` NextUpdate time.Time `json:"next_update"` GeneratedAt time.Time `json:"generated_at"` diff --git a/internal/domain/policy.go b/internal/domain/policy.go index af0dd08..770d9bf 100644 --- a/internal/domain/policy.go +++ b/internal/domain/policy.go @@ -21,12 +21,12 @@ type PolicyRule struct { type PolicyType string const ( - PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers" - PolicyTypeAllowedDomains PolicyType = "AllowedDomains" - PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata" - PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments" - PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime" - PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime" + PolicyTypeAllowedIssuers PolicyType = "AllowedIssuers" + PolicyTypeAllowedDomains PolicyType = "AllowedDomains" + PolicyTypeRequiredMetadata PolicyType = "RequiredMetadata" + PolicyTypeAllowedEnvironments PolicyType = "AllowedEnvironments" + PolicyTypeRenewalLeadTime PolicyType = "RenewalLeadTime" + PolicyTypeCertificateLifetime PolicyType = "CertificateLifetime" ) // PolicyViolation records an instance of a certificate violating a policy rule. diff --git a/internal/integration/e2e_test.go b/internal/integration/e2e_test.go index 5595496..f25dfc0 100644 --- a/internal/integration/e2e_test.go +++ b/internal/integration/e2e_test.go @@ -704,17 +704,17 @@ func TestM20EnhancedQueryAPI(t *testing.T) { // Setup: Create a certificate for testing now := time.Now() cert := &domain.ManagedCertificate{ - ID: "mc-m20-test-1", - Name: "M20 Test Cert", - CommonName: "m20.example.com", - Environment: "production", - Status: domain.CertificateStatusActive, - IssuerID: "iss-local", - OwnerID: "owner-ops", - TeamID: "team-platform", + ID: "mc-m20-test-1", + Name: "M20 Test Cert", + CommonName: "m20.example.com", + Environment: "production", + Status: domain.CertificateStatusActive, + IssuerID: "iss-local", + OwnerID: "owner-ops", + TeamID: "team-platform", CertificateProfileID: "prof-standard", - CreatedAt: now, - UpdatedAt: now, + CreatedAt: now, + UpdatedAt: now, } certRepo.certs["mc-m20-test-1"] = cert diff --git a/internal/repository/postgres/agent.go b/internal/repository/postgres/agent.go index 6a08d2a..408b9c0 100644 --- a/internal/repository/postgres/agent.go +++ b/internal/repository/postgres/agent.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "time" "github.com/google/uuid" diff --git a/internal/repository/postgres/agent_group.go b/internal/repository/postgres/agent_group.go index 27b046e..9be6446 100644 --- a/internal/repository/postgres/agent_group.go +++ b/internal/repository/postgres/agent_group.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "time" "github.com/shankar0123/certctl/internal/domain" diff --git a/internal/repository/postgres/db_test.go b/internal/repository/postgres/db_test.go index 887d462..509f198 100644 --- a/internal/repository/postgres/db_test.go +++ b/internal/repository/postgres/db_test.go @@ -52,11 +52,11 @@ func TestWrapPingError_AuthFailureGuidance(t *testing.T) { // Contract elements — the operator-facing string is what we ship. wantSubstrings := []string{ - "SQLSTATE 28P01", // operators grep on this - "POSTGRES_PASSWORD", // names the variable that traps - "first boot", // the mechanism in plain language - "down -v", // destructive remediation - "ALTER ROLE", // non-destructive remediation + "SQLSTATE 28P01", // operators grep on this + "POSTGRES_PASSWORD", // names the variable that traps + "first boot", // the mechanism in plain language + "down -v", // destructive remediation + "ALTER ROLE", // non-destructive remediation } for _, s := range wantSubstrings { if !strings.Contains(got, s) { diff --git a/internal/repository/postgres/health_check_test.go b/internal/repository/postgres/health_check_test.go index f9b132b..43d9f03 100644 --- a/internal/repository/postgres/health_check_test.go +++ b/internal/repository/postgres/health_check_test.go @@ -235,8 +235,8 @@ func TestHealthCheckRepository_ListDueForCheck(t *testing.T) { ctx := context.Background() now := time.Now().UTC().Truncate(time.Microsecond) - pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due - recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due + pastDue := now.Add(-10 * time.Minute) // > 300s ago, enabled → due + recent := now.Add(-30 * time.Second) // < 300s ago, enabled → not due // (a) enabled + null last_checked_at — NULLS FIRST puts this first a := newHealthCheck("hc-due-a", "a.example.com:443", domain.HealthStatusUnknown, true) diff --git a/internal/repository/postgres/issuer.go b/internal/repository/postgres/issuer.go index 2fd530f..0c2f84e 100644 --- a/internal/repository/postgres/issuer.go +++ b/internal/repository/postgres/issuer.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "github.com/google/uuid" "github.com/shankar0123/certctl/internal/domain" diff --git a/internal/repository/postgres/job.go b/internal/repository/postgres/job.go index 3a26579..d6076bd 100644 --- a/internal/repository/postgres/job.go +++ b/internal/repository/postgres/job.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "time" "github.com/google/uuid" diff --git a/internal/repository/postgres/network_scan.go b/internal/repository/postgres/network_scan.go index aacf3da..3983886 100644 --- a/internal/repository/postgres/network_scan.go +++ b/internal/repository/postgres/network_scan.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "time" "github.com/lib/pq" diff --git a/internal/repository/postgres/owner.go b/internal/repository/postgres/owner.go index 6d6d3a1..9d4523a 100644 --- a/internal/repository/postgres/owner.go +++ b/internal/repository/postgres/owner.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "github.com/google/uuid" "github.com/shankar0123/certctl/internal/domain" diff --git a/internal/repository/postgres/repo_test.go b/internal/repository/postgres/repo_test.go index 1d9f5fa..6571fdb 100644 --- a/internal/repository/postgres/repo_test.go +++ b/internal/repository/postgres/repo_test.go @@ -319,7 +319,7 @@ func TestCertificateRepository_GetExpiringCertificates(t *testing.T) { ID: tc.id, Name: tc.id, CommonName: tc.id + ".example.com", SANs: []string{}, OwnerID: ownerID, TeamID: teamID, IssuerID: issuerID, RenewalPolicyID: policyID, - Status: domain.CertificateStatusActive, + Status: domain.CertificateStatusActive, ExpiresAt: tc.expires, Tags: map[string]string{}, CreatedAt: now, UpdatedAt: now, } @@ -780,7 +780,7 @@ func TestJobRepository_CRUD(t *testing.T) { ID: "mc-job-test", Name: "job-test", CommonName: "job.example.com", SANs: []string{}, OwnerID: ownerID, TeamID: teamID, IssuerID: issuerID, RenewalPolicyID: policyID, - Status: domain.CertificateStatusActive, + Status: domain.CertificateStatusActive, ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{}, CreatedAt: now, UpdatedAt: now, } @@ -871,7 +871,7 @@ func TestRevocationRepository_CRUD(t *testing.T) { ID: "mc-rev-test", Name: "rev-test", CommonName: "rev.example.com", SANs: []string{}, OwnerID: ownerID, TeamID: teamID, IssuerID: issuerID, RenewalPolicyID: policyID, - Status: domain.CertificateStatusRevoked, + Status: domain.CertificateStatusRevoked, ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{}, CreatedAt: now, UpdatedAt: now, } @@ -1250,12 +1250,12 @@ func TestProfileRepository_CRUD(t *testing.T) { {Algorithm: "RSA", MinSize: 2048}, {Algorithm: "ECDSA", MinSize: 256}, }, - MaxTTLSeconds: 86400, - AllowedEKUs: []string{"serverAuth"}, - AllowShortLived: false, - Enabled: true, - CreatedAt: now, - UpdatedAt: now, + MaxTTLSeconds: 86400, + AllowedEKUs: []string{"serverAuth"}, + AllowShortLived: false, + Enabled: true, + CreatedAt: now, + UpdatedAt: now, } if err := repo.Create(ctx, profile); err != nil { @@ -1306,7 +1306,7 @@ func TestNotificationRepository_CRUD(t *testing.T) { ID: "mc-notif-test", Name: "notif-test", CommonName: "notif.example.com", SANs: []string{}, OwnerID: ownerID, TeamID: teamID, IssuerID: issuerID, RenewalPolicyID: policyID, - Status: domain.CertificateStatusActive, + Status: domain.CertificateStatusActive, ExpiresAt: now.Add(30 * 24 * time.Hour), Tags: map[string]string{}, CreatedAt: now, UpdatedAt: now, } @@ -1432,7 +1432,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) { ID: "mc-linked-cert", Name: "linked-cert", CommonName: "linked.example.com", SANs: []string{}, OwnerID: ownerID, TeamID: teamID, IssuerID: issuerID, RenewalPolicyID: policyID, - Status: domain.CertificateStatusActive, + Status: domain.CertificateStatusActive, ExpiresAt: now.Add(90 * 24 * time.Hour), Tags: map[string]string{}, CreatedAt: now, UpdatedAt: now, } @@ -1447,7 +1447,7 @@ func TestDiscoveryRepository_DiscoveredCertCRUD(t *testing.T) { NotBefore: ¬Before, NotAfter: ¬After, KeyAlgorithm: "RSA", KeySize: 2048, IsCA: false, PEMData: "---PEM---", SourcePath: "/etc/ssl/certs/disc.pem", SourceFormat: "PEM", AgentID: "agent-dcert-test", - Status: domain.DiscoveryStatusUnmanaged, + Status: domain.DiscoveryStatusUnmanaged, FirstSeenAt: now, LastSeenAt: now, CreatedAt: now, UpdatedAt: now, } @@ -1577,8 +1577,8 @@ func TestNetworkScanRepository_CRUD(t *testing.T) { target := &domain.NetworkScanTarget{ ID: "ns-test-1", Name: "Internal Network", - CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"}, - Ports: []int64{443, 8443}, + CIDRs: []string{"10.0.0.0/24", "192.168.1.0/24"}, + Ports: []int64{443, 8443}, Enabled: true, ScanIntervalHours: 6, TimeoutMs: 5000, CreatedAt: now, UpdatedAt: now, } diff --git a/internal/repository/postgres/revocation.go b/internal/repository/postgres/revocation.go index b9ae58c..9e2a6e1 100644 --- a/internal/repository/postgres/revocation.go +++ b/internal/repository/postgres/revocation.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "github.com/shankar0123/certctl/internal/domain" ) diff --git a/internal/repository/postgres/target.go b/internal/repository/postgres/target.go index 2ba4c79..63d902e 100644 --- a/internal/repository/postgres/target.go +++ b/internal/repository/postgres/target.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "github.com/google/uuid" "github.com/shankar0123/certctl/internal/domain" diff --git a/internal/repository/postgres/team.go b/internal/repository/postgres/team.go index d7ff679..c842294 100644 --- a/internal/repository/postgres/team.go +++ b/internal/repository/postgres/team.go @@ -1,10 +1,10 @@ package postgres import ( - "github.com/shankar0123/certctl/internal/repository" "context" "database/sql" "fmt" + "github.com/shankar0123/certctl/internal/repository" "github.com/google/uuid" "github.com/shankar0123/certctl/internal/domain" diff --git a/internal/scheduler/scheduler_test.go b/internal/scheduler/scheduler_test.go index 84af690..8a8d02f 100644 --- a/internal/scheduler/scheduler_test.go +++ b/internal/scheduler/scheduler_test.go @@ -11,14 +11,14 @@ import ( // mockRenewalService is a mock implementation for testing. type mockRenewalService struct { - mu sync.Mutex - callCount int - callTimes []time.Time - expireCallCount int - expireCallTimes []time.Time - slowDelay time.Duration - shouldError bool - blockCh chan struct{} // if non-nil, blocks until closed (ignores context) + mu sync.Mutex + callCount int + callTimes []time.Time + expireCallCount int + expireCallTimes []time.Time + slowDelay time.Duration + shouldError bool + blockCh chan struct{} // if non-nil, blocks until closed (ignores context) } func (m *mockRenewalService) CheckExpiringCertificates(ctx context.Context) error { @@ -138,7 +138,6 @@ func (m *mockJobService) RetryFailedJobs(ctx context.Context, maxRetries int) er return nil } - // ReapTimedOutJobs is the scheduler-driven counterpart to ProcessPendingJobs that // covers coverage gap I-003: JobService.ReapTimedOutJobs (via JobReaperService interface) // had no runtime caller prior to the jobTimeoutLoop being wired. diff --git a/internal/service/agent_group_test.go b/internal/service/agent_group_test.go index 41e81fe..fb2d323 100644 --- a/internal/service/agent_group_test.go +++ b/internal/service/agent_group_test.go @@ -12,16 +12,16 @@ import ( // mockAgentGroupRepo is a test implementation of AgentGroupRepository type mockAgentGroupRepo struct { - groups map[string]*domain.AgentGroup - members map[string][]*domain.Agent - CreateErr error - UpdateErr error - DeleteErr error - GetErr error - ListErr error - ListMembersErr error - AddMemberErr error - RemoveMemberErr error + groups map[string]*domain.AgentGroup + members map[string][]*domain.Agent + CreateErr error + UpdateErr error + DeleteErr error + GetErr error + ListErr error + ListMembersErr error + AddMemberErr error + RemoveMemberErr error } func newMockAgentGroupRepository() *mockAgentGroupRepo { diff --git a/internal/service/agent_round_out_test.go b/internal/service/agent_round_out_test.go index b76330f..e1e822c 100644 --- a/internal/service/agent_round_out_test.go +++ b/internal/service/agent_round_out_test.go @@ -109,9 +109,9 @@ func TestAgentService_UpdateJobStatus_DelegatesToReportJobStatus(t *testing.T) { svc, repo, _, jobRepo, _ := newTestAgentSvc(t) repo.Agents["a-1"] = &domain.Agent{ID: "a-1", Status: domain.AgentStatusOnline} jobRepo.Jobs["j-1"] = &domain.Job{ - ID: "j-1", - AgentID: strPtr("a-1"), - Status: domain.JobStatusRunning, + ID: "j-1", + AgentID: strPtr("a-1"), + Status: domain.JobStatusRunning, } err := svc.UpdateJobStatus(context.Background(), "a-1", "j-1", "Completed", "") if err != nil { diff --git a/internal/service/agent_test.go b/internal/service/agent_test.go index 7271feb..8dc1bbc 100644 --- a/internal/service/agent_test.go +++ b/internal/service/agent_test.go @@ -10,7 +10,6 @@ import ( "github.com/shankar0123/certctl/internal/domain" ) - func TestRegisterAgent(t *testing.T) { ctx := context.Background() agentRepo := &mockAgentRepo{ diff --git a/internal/service/audit_redact.go b/internal/service/audit_redact.go index 1b9aeac..127fb0b 100644 --- a/internal/service/audit_redact.go +++ b/internal/service/audit_redact.go @@ -73,29 +73,29 @@ var credentialKeys = map[string]bool{ // Note `ip_address` is debatable — useful for forensics but flagged by // GDPR Art. 32 — defaulting to redact, operators can audit + adjust. var piiKeys = map[string]bool{ - "email": true, - "email_address": true, - "phone": true, - "phone_number": true, - "telephone": true, - "ssn": true, + "email": true, + "email_address": true, + "phone": true, + "phone_number": true, + "telephone": true, + "ssn": true, "social_security": true, - "dob": true, - "date_of_birth": true, - "name": true, - "full_name": true, - "first_name": true, - "last_name": true, - "surname": true, - "address": true, - "street": true, - "street_address": true, - "city": true, - "postal_code": true, - "zip": true, - "zipcode": true, - "ip": true, - "ip_address": true, + "dob": true, + "date_of_birth": true, + "name": true, + "full_name": true, + "first_name": true, + "last_name": true, + "surname": true, + "address": true, + "street": true, + "street_address": true, + "city": true, + "postal_code": true, + "zip": true, + "zipcode": true, + "ip": true, + "ip_address": true, } // RedactDetailsForAudit walks a details map and returns a NEW map with diff --git a/internal/service/audit_redact_test.go b/internal/service/audit_redact_test.go index 953297f..af9fe5c 100644 --- a/internal/service/audit_redact_test.go +++ b/internal/service/audit_redact_test.go @@ -30,8 +30,8 @@ func TestRedactDetailsForAudit_CredentialKeys(t *testing.T) { for _, key := range cases { t.Run(key, func(t *testing.T) { in := map[string]interface{}{ - key: "sensitive-value-do-not-leak", - "non_sensitive_id": "ok-public-id", + key: "sensitive-value-do-not-leak", + "non_sensitive_id": "ok-public-id", } out := RedactDetailsForAudit(in) if out[key] != "[REDACTED:CREDENTIAL]" { @@ -70,7 +70,7 @@ func TestRedactDetailsForAudit_NestedMap(t *testing.T) { in := map[string]interface{}{ "resource_id": "iss-prod", "config": map[string]interface{}{ - "endpoint": "https://acme.example.com", + "endpoint": "https://acme.example.com", "eab_secret": "do-not-leak-this-secret", "contact": map[string]interface{}{ "email": "ops@example.com", @@ -159,8 +159,8 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) { // Maps with no sensitive keys should NOT have a redacted_keys array // — clutter-free for the common case. in := map[string]interface{}{ - "action": "create_certificate", - "cert_id": "mc-prod-001", + "action": "create_certificate", + "cert_id": "mc-prod-001", "latency_ms": float64(42), } out := RedactDetailsForAudit(in) @@ -171,7 +171,7 @@ func TestRedactDetailsForAudit_NoRedactionPath(t *testing.T) { func TestRedactDetailsForAudit_DoesNotMutateInput(t *testing.T) { in := map[string]interface{}{ - "api_key": "secret-do-not-leak", + "api_key": "secret-do-not-leak", "resource": "iss-prod", } _ = RedactDetailsForAudit(in) @@ -197,8 +197,8 @@ func TestRedactDetailsForAudit_JSONRoundTrip(t *testing.T) { // The redacted map MUST round-trip through json.Marshal (the // AuditService persistence path). Catches type-assertion regressions. in := map[string]interface{}{ - "reason": "compromised-key", - "api_key": "leak-me", + "reason": "compromised-key", + "api_key": "leak-me", "contacts": []interface{}{ map[string]interface{}{"email": "ops@example.com"}, }, diff --git a/internal/service/bulk_reassignment_test.go b/internal/service/bulk_reassignment_test.go index 37aaa05..1a88af5 100644 --- a/internal/service/bulk_reassignment_test.go +++ b/internal/service/bulk_reassignment_test.go @@ -67,8 +67,8 @@ func TestBulkReassign_HappyPath(t *testing.T) { func TestBulkReassign_SkipsAlreadyOwned(t *testing.T) { svc, certRepo, ownerRepo, _ := newBulkReassignmentTestService() addOwner(ownerRepo, "o-bob") - addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target - addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign + addOwnedCert(certRepo, "mc-1", "o-bob", "") // already owned by target + addOwnedCert(certRepo, "mc-2", "o-alice", "") // needs reassign res, err := svc.BulkReassign(context.Background(), domain.BulkReassignmentRequest{ diff --git a/internal/service/bulk_revocation_test.go b/internal/service/bulk_revocation_test.go index d01588c..f4f2bc6 100644 --- a/internal/service/bulk_revocation_test.go +++ b/internal/service/bulk_revocation_test.go @@ -32,11 +32,11 @@ func newBulkRevocationTestService() (*BulkRevocationService, *mockCertRepo, *moc func addTestCert(repo *mockCertRepo, id, status, issuerID string) { cert := &domain.ManagedCertificate{ - ID: id, + ID: id, CommonName: id + ".example.com", - Status: domain.CertificateStatus(status), - IssuerID: issuerID, - ExpiresAt: time.Now().AddDate(0, 6, 0), + Status: domain.CertificateStatus(status), + IssuerID: issuerID, + ExpiresAt: time.Now().AddDate(0, 6, 0), } repo.AddCert(cert) // Add a version with serial number (needed by RevokeCertificateWithActor) @@ -54,13 +54,13 @@ func addTestCert(repo *mockCertRepo, id, status, issuerID string) { func addTestCertWithProfile(repo *mockCertRepo, id, status, issuerID, profileID, ownerID string) { cert := &domain.ManagedCertificate{ - ID: id, - CommonName: id + ".example.com", - Status: domain.CertificateStatus(status), - IssuerID: issuerID, + ID: id, + CommonName: id + ".example.com", + Status: domain.CertificateStatus(status), + IssuerID: issuerID, CertificateProfileID: profileID, - OwnerID: ownerID, - ExpiresAt: time.Now().AddDate(0, 6, 0), + OwnerID: ownerID, + ExpiresAt: time.Now().AddDate(0, 6, 0), } repo.AddCert(cert) repo.Versions[id] = []*domain.CertificateVersion{ @@ -272,11 +272,11 @@ func TestBulkRevoke_PartialFailure(t *testing.T) { addTestCert(certRepo, "mc-1", "Active", "iss-local") // mc-2 is active but has NO version — RevokeCertificateWithActor will fail on GetLatestVersion cert2 := &domain.ManagedCertificate{ - ID: "mc-2", + ID: "mc-2", CommonName: "mc-2.example.com", - Status: domain.CertificateStatusActive, - IssuerID: "iss-local", - ExpiresAt: time.Now().AddDate(0, 6, 0), + Status: domain.CertificateStatusActive, + IssuerID: "iss-local", + ExpiresAt: time.Now().AddDate(0, 6, 0), } certRepo.AddCert(cert2) // Don't add versions for mc-2 so GetLatestVersion returns errNotFound diff --git a/internal/service/deployment_test.go b/internal/service/deployment_test.go index bb34a68..4c36603 100644 --- a/internal/service/deployment_test.go +++ b/internal/service/deployment_test.go @@ -266,17 +266,17 @@ func TestDeploymentService_ProcessDeploymentJob_Success(t *testing.T) { // Add agent with recent heartbeat now := time.Now() agent := &domain.Agent{ - ID: "agent-1", - Name: "Test Agent", - Hostname: "agent.example.com", - Status: domain.AgentStatusOnline, - LastHeartbeatAt: &now, - RegisteredAt: time.Now(), - APIKeyHash: "hash-1", - OS: "linux", - Architecture: "amd64", - IPAddress: "192.168.1.1", - Version: "1.0.0", + ID: "agent-1", + Name: "Test Agent", + Hostname: "agent.example.com", + Status: domain.AgentStatusOnline, + LastHeartbeatAt: &now, + RegisteredAt: time.Now(), + APIKeyHash: "hash-1", + OS: "linux", + Architecture: "amd64", + IPAddress: "192.168.1.1", + Version: "1.0.0", } agentRepo.AddAgent(agent) diff --git a/internal/service/digest.go b/internal/service/digest.go index 7002101..2a1fde7 100644 --- a/internal/service/digest.go +++ b/internal/service/digest.go @@ -31,20 +31,20 @@ type HTMLEmailSender interface { // DigestData holds the aggregated data for a digest email. type DigestData struct { - GeneratedAt time.Time `json:"generated_at"` - TotalCertificates int64 `json:"total_certificates"` - ExpiringCertificates int64 `json:"expiring_certificates"` - ExpiredCertificates int64 `json:"expired_certificates"` - RevokedCertificates int64 `json:"revoked_certificates"` - ActiveAgents int64 `json:"active_agents"` - OfflineAgents int64 `json:"offline_agents"` - TotalAgents int64 `json:"total_agents"` - PendingJobs int64 `json:"pending_jobs"` - FailedJobs int64 `json:"failed_jobs"` - CompletedJobs int64 `json:"completed_jobs"` - ExpiringCerts []DigestCertEntry `json:"expiring_certs"` - RecentFailures []DigestJobEntry `json:"recent_failures"` - StatusCounts []DigestStatusCount `json:"status_counts"` + GeneratedAt time.Time `json:"generated_at"` + TotalCertificates int64 `json:"total_certificates"` + ExpiringCertificates int64 `json:"expiring_certificates"` + ExpiredCertificates int64 `json:"expired_certificates"` + RevokedCertificates int64 `json:"revoked_certificates"` + ActiveAgents int64 `json:"active_agents"` + OfflineAgents int64 `json:"offline_agents"` + TotalAgents int64 `json:"total_agents"` + PendingJobs int64 `json:"pending_jobs"` + FailedJobs int64 `json:"failed_jobs"` + CompletedJobs int64 `json:"completed_jobs"` + ExpiringCerts []DigestCertEntry `json:"expiring_certs"` + RecentFailures []DigestJobEntry `json:"recent_failures"` + StatusCounts []DigestStatusCount `json:"status_counts"` } // DigestCertEntry represents a certificate entry in the digest. diff --git a/internal/service/discovery_test.go b/internal/service/discovery_test.go index 548738b..085fc6b 100644 --- a/internal/service/discovery_test.go +++ b/internal/service/discovery_test.go @@ -12,17 +12,17 @@ import ( // mockDiscoveryRepo is a test implementation of DiscoveryRepository type mockDiscoveryRepo struct { - Scans map[string]*domain.DiscoveryScan - Discovered map[string]*domain.DiscoveredCertificate - CreateScanErr error - GetScanErr error - ListScansErr error + Scans map[string]*domain.DiscoveryScan + Discovered map[string]*domain.DiscoveredCertificate + CreateScanErr error + GetScanErr error + ListScansErr error CreateDiscoveredErr error - GetDiscoveredErr error - ListDiscoveredErr error - UpdateStatusErr error + GetDiscoveredErr error + ListDiscoveredErr error + UpdateStatusErr error GetByFingerprintErr error - CountByStatusErr error + CountByStatusErr error } func newMockDiscoveryRepository() *mockDiscoveryRepo { @@ -268,20 +268,20 @@ func TestListDiscovered_Success(t *testing.T) { now := time.Now() cert1 := &domain.DiscoveredCertificate{ - ID: "dcert-1", - AgentID: "agent-1", - CommonName: "example.com", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + ID: "dcert-1", + AgentID: "agent-1", + CommonName: "example.com", + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } cert2 := &domain.DiscoveredCertificate{ - ID: "dcert-2", - AgentID: "agent-1", - CommonName: "api.example.com", - Status: domain.DiscoveryStatusManaged, - CreatedAt: now, - UpdatedAt: now, + ID: "dcert-2", + AgentID: "agent-1", + CommonName: "api.example.com", + Status: domain.DiscoveryStatusManaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[cert1.ID] = cert1 discoveryRepo.Discovered[cert2.ID] = cert2 @@ -304,20 +304,20 @@ func TestListDiscovered_WithStatusFilter(t *testing.T) { now := time.Now() cert1 := &domain.DiscoveredCertificate{ - ID: "dcert-1", - AgentID: "agent-1", + ID: "dcert-1", + AgentID: "agent-1", CommonName: "example.com", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } cert2 := &domain.DiscoveredCertificate{ - ID: "dcert-2", - AgentID: "agent-1", + ID: "dcert-2", + AgentID: "agent-1", CommonName: "api.example.com", - Status: domain.DiscoveryStatusManaged, - CreatedAt: now, - UpdatedAt: now, + Status: domain.DiscoveryStatusManaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[cert1.ID] = cert1 discoveryRepo.Discovered[cert2.ID] = cert2 @@ -340,11 +340,11 @@ func TestGetDiscovered_Success(t *testing.T) { now := time.Now() cert := &domain.DiscoveredCertificate{ - ID: "dcert-1", - CommonName: "example.com", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + ID: "dcert-1", + CommonName: "example.com", + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[cert.ID] = cert @@ -363,12 +363,12 @@ func TestClaimDiscovered_Success(t *testing.T) { now := time.Now() discoveredCert := &domain.DiscoveredCertificate{ - ID: "dcert-1", - CommonName: "example.com", - FingerprintSHA256: "abc123", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + ID: "dcert-1", + CommonName: "example.com", + FingerprintSHA256: "abc123", + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[discoveredCert.ID] = discoveredCert @@ -415,11 +415,11 @@ func TestClaimDiscovered_MissingManagedCertID(t *testing.T) { now := time.Now() cert := &domain.DiscoveredCertificate{ - ID: "dcert-1", + ID: "dcert-1", CommonName: "example.com", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[cert.ID] = cert @@ -434,11 +434,11 @@ func TestClaimDiscovered_ManagedCertNotFound(t *testing.T) { now := time.Now() cert := &domain.DiscoveredCertificate{ - ID: "dcert-1", + ID: "dcert-1", CommonName: "example.com", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[cert.ID] = cert @@ -456,11 +456,11 @@ func TestDismissDiscovered_Success(t *testing.T) { now := time.Now() cert := &domain.DiscoveredCertificate{ - ID: "dcert-1", + ID: "dcert-1", CommonName: "example.com", - Status: domain.DiscoveryStatusUnmanaged, - CreatedAt: now, - UpdatedAt: now, + Status: domain.DiscoveryStatusUnmanaged, + CreatedAt: now, + UpdatedAt: now, } discoveryRepo.Discovered[cert.ID] = cert diff --git a/internal/service/export_test.go b/internal/service/export_test.go index 997dce6..72db887 100644 --- a/internal/service/export_test.go +++ b/internal/service/export_test.go @@ -71,10 +71,10 @@ func TestExportPEM_Success(t *testing.T) { Status: domain.CertificateStatusActive, }, &domain.CertificateVersion{ - ID: "cv-1", + ID: "cv-1", CertificateID: "mc-test-1", - SerialNumber: "abc123", - PEMChain: fullPEM, + SerialNumber: "abc123", + PEMChain: fullPEM, }, ) auditSvc := &AuditService{auditRepo: &mockAuditRepo{}} diff --git a/internal/service/health_check_test.go b/internal/service/health_check_test.go index b00c263..02aa826 100644 --- a/internal/service/health_check_test.go +++ b/internal/service/health_check_test.go @@ -14,19 +14,19 @@ import ( // mockHealthCheckRepo implements the HealthCheckRepository interface for testing. type mockHealthCheckRepo struct { - checks map[string]*domain.EndpointHealthCheck - history []*domain.HealthHistoryEntry - createErr error - getErr error - updateErr error - deleteErr error - listErr error - listDueErr error - getHistoryErr error - recordHistoryErr error - purgeHistoryErr error - getSummaryErr error - getSummaryResult *domain.HealthCheckSummary + checks map[string]*domain.EndpointHealthCheck + history []*domain.HealthHistoryEntry + createErr error + getErr error + updateErr error + deleteErr error + listErr error + listDueErr error + getHistoryErr error + recordHistoryErr error + purgeHistoryErr error + getSummaryErr error + getSummaryResult *domain.HealthCheckSummary } func newMockHealthCheckRepo() *mockHealthCheckRepo { @@ -151,9 +151,9 @@ func TestHealthCheckService_Create_Success(t *testing.T) { svc := NewHealthCheckService(repo, nil, logger, 10, 5*time.Second, 30*24*time.Hour, false) check := &domain.EndpointHealthCheck{ - Endpoint: "example.com:443", - Status: domain.HealthStatusUnknown, - Enabled: true, + Endpoint: "example.com:443", + Status: domain.HealthStatusUnknown, + Enabled: true, CheckIntervalSecs: 300, } diff --git a/internal/service/job.go b/internal/service/job.go index 35598d2..8135202 100644 --- a/internal/service/job.go +++ b/internal/service/job.go @@ -1,12 +1,12 @@ package service import ( - "time" "context" "errors" "fmt" "log/slog" "strings" + "time" "github.com/shankar0123/certctl/internal/domain" "github.com/shankar0123/certctl/internal/repository" diff --git a/internal/service/job_offline_agent_reaper_test.go b/internal/service/job_offline_agent_reaper_test.go index d3bd6ed..83220d3 100644 --- a/internal/service/job_offline_agent_reaper_test.go +++ b/internal/service/job_offline_agent_reaper_test.go @@ -36,9 +36,9 @@ func mkRunningJob(id, agentID string) *domain.Job { a := agentID now := time.Now() return &domain.Job{ - ID: id, - AgentID: &a, - Status: domain.JobStatusRunning, + ID: id, + AgentID: &a, + Status: domain.JobStatusRunning, CreatedAt: now.Add(-2 * time.Hour), } } diff --git a/internal/service/job_test.go b/internal/service/job_test.go index a67e1ed..91bf38f 100644 --- a/internal/service/job_test.go +++ b/internal/service/job_test.go @@ -871,8 +871,8 @@ func TestJobService_ReapTimedOutJobs_ContinuesOnIndividualUpdateFailure(t *testi jobA.ID: jobA, jobB.ID: jobB, }, - StatusUpdates: make(map[string]domain.JobStatus), - UpdateErrorByID: make(map[string]error), + StatusUpdates: make(map[string]domain.JobStatus), + UpdateErrorByID: make(map[string]error), UpdateErrorByIDMu: sync.Mutex{}, } // Make Update fail only for jobA @@ -892,7 +892,7 @@ func TestJobService_ReapTimedOutJobs_ContinuesOnIndividualUpdateFailure(t *testi jobAAfter := jobRepo.Jobs[jobA.ID] jobBAfter := jobRepo.Jobs[jobB.ID] if jobAAfter.Status != domain.JobStatusFailed || jobBAfter.Status != domain.JobStatusFailed { - t.Fatalf("expected both jobs status Failed (modified before Update), got A=%s B=%s", + t.Fatalf("expected both jobs status Failed (modified before Update), got A=%s B=%s", jobAAfter.Status, jobBAfter.Status) } diff --git a/internal/service/network_scan_test.go b/internal/service/network_scan_test.go index 48803c6..5974c49 100644 --- a/internal/service/network_scan_test.go +++ b/internal/service/network_scan_test.go @@ -377,8 +377,8 @@ func TestExpandCIDR_AllowsPrivateRanges(t *testing.T) { cidr string min int }{ - {"10/8 sample", "10.0.0.0/30", 2}, // 2 usable (after removing network/broadcast) - {"172.16/12 sample", "172.16.0.0/30", 2}, // 2 usable + {"10/8 sample", "10.0.0.0/30", 2}, // 2 usable (after removing network/broadcast) + {"172.16/12 sample", "172.16.0.0/30", 2}, // 2 usable {"192.168/16 sample", "192.168.1.1/32", 1}, // Single IP } diff --git a/internal/service/ocsp_counters.go b/internal/service/ocsp_counters.go index 5ecf717..4b3f5c2 100644 --- a/internal/service/ocsp_counters.go +++ b/internal/service/ocsp_counters.go @@ -43,16 +43,16 @@ import "sync/atomic" // New labels MUST also be added to OCSPCounters.Snapshot AND to the // Prometheus exposer in Phase 8. type OCSPCounters struct { - requestGET atomic.Uint64 - requestPOST atomic.Uint64 - requestSuccess atomic.Uint64 - requestInvalid atomic.Uint64 - issuerNotFound atomic.Uint64 - certNotFound atomic.Uint64 - signingFailed atomic.Uint64 - nonceEchoed atomic.Uint64 - nonceMalformed atomic.Uint64 - rateLimited atomic.Uint64 + requestGET atomic.Uint64 + requestPOST atomic.Uint64 + requestSuccess atomic.Uint64 + requestInvalid atomic.Uint64 + issuerNotFound atomic.Uint64 + certNotFound atomic.Uint64 + signingFailed atomic.Uint64 + nonceEchoed atomic.Uint64 + nonceMalformed atomic.Uint64 + rateLimited atomic.Uint64 } // NewOCSPCounters constructs a zero-value counter table. The caller diff --git a/internal/service/renewal_test.go b/internal/service/renewal_test.go index cf0044f..38272cf 100644 --- a/internal/service/renewal_test.go +++ b/internal/service/renewal_test.go @@ -977,7 +977,7 @@ func TestCheckExpiringCertificates_ARI_ShouldRenewNow(t *testing.T) { 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(), + CreatedAt: time.Now(), UpdatedAt: time.Now(), } policyRepo.AddPolicy(policy) @@ -1050,7 +1050,7 @@ func TestCheckExpiringCertificates_ARI_NotYet(t *testing.T) { 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(), + CreatedAt: time.Now(), UpdatedAt: time.Now(), } policyRepo.AddPolicy(policy) @@ -1110,7 +1110,7 @@ func TestCheckExpiringCertificates_ARI_NilResult_FallsThrough(t *testing.T) { 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(), + CreatedAt: time.Now(), UpdatedAt: time.Now(), } policyRepo.AddPolicy(policy) @@ -1178,7 +1178,7 @@ func TestCheckExpiringCertificates_ARI_Error_FallsThrough(t *testing.T) { 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(), + CreatedAt: time.Now(), UpdatedAt: time.Now(), } policyRepo.AddPolicy(policy) @@ -1313,7 +1313,6 @@ func TestFailJob_SetsFailedStatus(t *testing.T) { } } - // --- CreateDeploymentJobs Tests --- func TestCreateDeploymentJobs_PartialFailure(t *testing.T) { @@ -1331,10 +1330,10 @@ func TestCreateDeploymentJobs_PartialFailure(t *testing.T) { // Create certificate cert := &domain.ManagedCertificate{ - ID: "mc-partial", + ID: "mc-partial", CommonName: "test.example.com", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } certRepo.AddCert(cert) @@ -1383,5 +1382,4 @@ func TestCreateDeploymentJobs_PartialFailure(t *testing.T) { } } - // stringPtr is defined in notification_test.go diff --git a/internal/service/revocation_svc.go b/internal/service/revocation_svc.go index c167ceb..987cfb9 100644 --- a/internal/service/revocation_svc.go +++ b/internal/service/revocation_svc.go @@ -13,11 +13,11 @@ import ( // RevocationSvc provides revocation-related business logic. // It handles certificate revocation, revocation notifications, and issuer coordination. type RevocationSvc struct { - certRepo repository.CertificateRepository - revocationRepo repository.RevocationRepository - auditService *AuditService - notificationSvc *NotificationService - issuerRegistry *IssuerRegistry + certRepo repository.CertificateRepository + revocationRepo repository.RevocationRepository + auditService *AuditService + notificationSvc *NotificationService + issuerRegistry *IssuerRegistry // ocspCacheInvalidator — production hardening II Phase 2 load- // bearing security wire. After a successful revocation, the // service MUST invalidate the OCSP response cache for this diff --git a/internal/service/shortlived_test.go b/internal/service/shortlived_test.go index d64a188..dd28dff 100644 --- a/internal/service/shortlived_test.go +++ b/internal/service/shortlived_test.go @@ -48,31 +48,31 @@ func TestExpireShortLivedCertificates_Success(t *testing.T) { // Create a short-lived profile (TTL < 1 hour = 3600 seconds) shortLivedProfile := &domain.CertificateProfile{ - ID: "prof-short", - Name: "Short-Lived", - MaxTTLSeconds: 300, // 5 minutes - AllowShortLived: true, - Enabled: true, + ID: "prof-short", + Name: "Short-Lived", + MaxTTLSeconds: 300, // 5 minutes + AllowShortLived: true, + Enabled: true, AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(), - AllowedEKUs: domain.DefaultEKUs(), - CreatedAt: now, - UpdatedAt: now, + AllowedEKUs: domain.DefaultEKUs(), + CreatedAt: now, + UpdatedAt: now, } profileRepo.AddProfile(shortLivedProfile) // Create an active certificate that has already expired expiredCert := &domain.ManagedCertificate{ - ID: "mc-expired-short", - Name: "Expired Short-Lived Cert", - CommonName: "short.example.com", - SANs: []string{}, - IssuerID: "iss-test", - CertificateProfileID: "prof-short", - Status: domain.CertificateStatusActive, - ExpiresAt: now.Add(-5 * time.Minute), // Already expired - CreatedAt: now.Add(-15 * time.Minute), - UpdatedAt: now.Add(-5 * time.Minute), - Tags: make(map[string]string), + ID: "mc-expired-short", + Name: "Expired Short-Lived Cert", + CommonName: "short.example.com", + SANs: []string{}, + IssuerID: "iss-test", + CertificateProfileID: "prof-short", + Status: domain.CertificateStatusActive, + ExpiresAt: now.Add(-5 * time.Minute), // Already expired + CreatedAt: now.Add(-15 * time.Minute), + UpdatedAt: now.Add(-5 * time.Minute), + Tags: make(map[string]string), } certRepo.AddCert(expiredCert) @@ -218,31 +218,31 @@ func TestExpireShortLivedCertificates_PartialUpdateError(t *testing.T) { // Create a short-lived profile shortLivedProfile := &domain.CertificateProfile{ - ID: "prof-short", - Name: "Short-Lived", - MaxTTLSeconds: 300, - AllowShortLived: true, - Enabled: true, + ID: "prof-short", + Name: "Short-Lived", + MaxTTLSeconds: 300, + AllowShortLived: true, + Enabled: true, AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(), - AllowedEKUs: domain.DefaultEKUs(), - CreatedAt: now, - UpdatedAt: now, + AllowedEKUs: domain.DefaultEKUs(), + CreatedAt: now, + UpdatedAt: now, } profileRepo.AddProfile(shortLivedProfile) // Create a certificate with a failing update expiredCert := &domain.ManagedCertificate{ - ID: "mc-expired-fail", - Name: "Expired Cert That Will Fail", - CommonName: "fail.example.com", - SANs: []string{}, - IssuerID: "iss-test", - CertificateProfileID: "prof-short", - Status: domain.CertificateStatusActive, - ExpiresAt: now.Add(-5 * time.Minute), - CreatedAt: now.Add(-15 * time.Minute), - UpdatedAt: now.Add(-5 * time.Minute), - Tags: make(map[string]string), + ID: "mc-expired-fail", + Name: "Expired Cert That Will Fail", + CommonName: "fail.example.com", + SANs: []string{}, + IssuerID: "iss-test", + CertificateProfileID: "prof-short", + Status: domain.CertificateStatusActive, + ExpiresAt: now.Add(-5 * time.Minute), + CreatedAt: now.Add(-15 * time.Minute), + UpdatedAt: now.Add(-5 * time.Minute), + Tags: make(map[string]string), } certRepo.AddCert(expiredCert) @@ -275,31 +275,31 @@ func TestExpireShortLivedCertificates_AlreadyExpired(t *testing.T) { // Create a short-lived profile shortLivedProfile := &domain.CertificateProfile{ - ID: "prof-short", - Name: "Short-Lived", - MaxTTLSeconds: 300, - AllowShortLived: true, - Enabled: true, + ID: "prof-short", + Name: "Short-Lived", + MaxTTLSeconds: 300, + AllowShortLived: true, + Enabled: true, AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(), - AllowedEKUs: domain.DefaultEKUs(), - CreatedAt: now, - UpdatedAt: now, + AllowedEKUs: domain.DefaultEKUs(), + CreatedAt: now, + UpdatedAt: now, } profileRepo.AddProfile(shortLivedProfile) // Create a certificate that's already in Expired status alreadyExpiredCert := &domain.ManagedCertificate{ - ID: "mc-already-expired", - Name: "Already Expired Cert", - CommonName: "already-expired.example.com", - SANs: []string{}, - IssuerID: "iss-test", - CertificateProfileID: "prof-short", - Status: domain.CertificateStatusExpired, // Already expired - ExpiresAt: now.Add(-30 * time.Minute), - CreatedAt: now.Add(-45 * time.Minute), - UpdatedAt: now.Add(-10 * time.Minute), - Tags: make(map[string]string), + ID: "mc-already-expired", + Name: "Already Expired Cert", + CommonName: "already-expired.example.com", + SANs: []string{}, + IssuerID: "iss-test", + CertificateProfileID: "prof-short", + Status: domain.CertificateStatusExpired, // Already expired + ExpiresAt: now.Add(-30 * time.Minute), + CreatedAt: now.Add(-45 * time.Minute), + UpdatedAt: now.Add(-10 * time.Minute), + Tags: make(map[string]string), } certRepo.AddCert(alreadyExpiredCert) @@ -329,31 +329,31 @@ func TestExpireShortLivedCertificates_ProfileNotShortLived(t *testing.T) { // Create a regular (not short-lived) profile with TTL > 1 hour regularProfile := &domain.CertificateProfile{ - ID: "prof-regular", - Name: "Regular", - MaxTTLSeconds: 86400, // 24 hours - AllowShortLived: false, - Enabled: true, + ID: "prof-regular", + Name: "Regular", + MaxTTLSeconds: 86400, // 24 hours + AllowShortLived: false, + Enabled: true, AllowedKeyAlgorithms: domain.DefaultKeyAlgorithms(), - AllowedEKUs: domain.DefaultEKUs(), - CreatedAt: now, - UpdatedAt: now, + AllowedEKUs: domain.DefaultEKUs(), + CreatedAt: now, + UpdatedAt: now, } profileRepo.AddProfile(regularProfile) // Create an expired certificate with the regular profile expiredCert := &domain.ManagedCertificate{ - ID: "mc-expired-regular", - Name: "Expired Regular Cert", - CommonName: "regular.example.com", - SANs: []string{}, - IssuerID: "iss-test", - CertificateProfileID: "prof-regular", - Status: domain.CertificateStatusActive, - ExpiresAt: now.Add(-1 * time.Hour), - CreatedAt: now.Add(-25 * time.Hour), - UpdatedAt: now.Add(-1 * time.Hour), - Tags: make(map[string]string), + ID: "mc-expired-regular", + Name: "Expired Regular Cert", + CommonName: "regular.example.com", + SANs: []string{}, + IssuerID: "iss-test", + CertificateProfileID: "prof-regular", + Status: domain.CertificateStatusActive, + ExpiresAt: now.Add(-1 * time.Hour), + CreatedAt: now.Add(-25 * time.Hour), + UpdatedAt: now.Add(-1 * time.Hour), + Tags: make(map[string]string), } certRepo.AddCert(expiredCert) diff --git a/internal/service/target.go b/internal/service/target.go index 475bfbf..e42a4b0 100644 --- a/internal/service/target.go +++ b/internal/service/target.go @@ -22,16 +22,16 @@ var ErrAgentNotFound = errors.New("referenced agent does not exist") // validTargetTypes is the set of allowed target types for validation. var validTargetTypes = map[domain.TargetType]bool{ - domain.TargetTypeNGINX: true, - domain.TargetTypeApache: true, - domain.TargetTypeHAProxy: true, - domain.TargetTypeF5: true, - domain.TargetTypeIIS: true, - domain.TargetTypeTraefik: true, - domain.TargetTypeCaddy: true, - domain.TargetTypeEnvoy: true, - domain.TargetTypePostfix: true, - domain.TargetTypeDovecot: true, + domain.TargetTypeNGINX: true, + domain.TargetTypeApache: true, + domain.TargetTypeHAProxy: true, + domain.TargetTypeF5: true, + domain.TargetTypeIIS: true, + domain.TargetTypeTraefik: true, + domain.TargetTypeCaddy: true, + domain.TargetTypeEnvoy: true, + domain.TargetTypePostfix: true, + domain.TargetTypeDovecot: true, domain.TargetTypeSSH: true, domain.TargetTypeWinCertStore: true, domain.TargetTypeJavaKeystore: true, diff --git a/internal/service/verification.go b/internal/service/verification.go index 076f988..ff99311 100644 --- a/internal/service/verification.go +++ b/internal/service/verification.go @@ -11,9 +11,9 @@ import ( // VerificationService handles recording and querying certificate deployment verification results. type VerificationService struct { - jobRepo repository.JobRepository - auditService *AuditService - logger *slog.Logger + jobRepo repository.JobRepository + auditService *AuditService + logger *slog.Logger } // NewVerificationService creates a new verification service. @@ -23,9 +23,9 @@ func NewVerificationService( logger *slog.Logger, ) *VerificationService { return &VerificationService{ - jobRepo: jobRepo, - auditService: auditService, - logger: logger, + jobRepo: jobRepo, + auditService: auditService, + logger: logger, } } @@ -76,11 +76,11 @@ func (s *VerificationService) RecordVerificationResult(ctx context.Context, resu // Record audit event auditEvent := "job_verification_success" auditDetails := map[string]interface{}{ - "job_id": result.JobID, - "target_id": result.TargetID, - "expected_fingerprint": result.ExpectedFingerprint, - "actual_fingerprint": result.ActualFingerprint, - "verified": result.Verified, + "job_id": result.JobID, + "target_id": result.TargetID, + "expected_fingerprint": result.ExpectedFingerprint, + "actual_fingerprint": result.ActualFingerprint, + "verified": result.Verified, } if result.Error != "" { @@ -114,7 +114,7 @@ func (s *VerificationService) GetVerificationResult(ctx context.Context, jobID s } result := &domain.VerificationResult{ - JobID: job.ID, + JobID: job.ID, Verified: job.VerificationStatus == domain.VerificationSuccess, } diff --git a/internal/tlsprobe/probe.go b/internal/tlsprobe/probe.go index 48b4c1f..340e9cd 100644 --- a/internal/tlsprobe/probe.go +++ b/internal/tlsprobe/probe.go @@ -15,18 +15,18 @@ import ( // ProbeResult contains the result of probing a TLS endpoint. type ProbeResult struct { - Address string `json:"address"` - Success bool `json:"success"` - Fingerprint string `json:"fingerprint"` // SHA-256 hex fingerprint of leaf cert - TLSVersion string `json:"tls_version"` // e.g. "TLS 1.3" - CipherSuite string `json:"cipher_suite"` // e.g. "TLS_AES_128_GCM_SHA256" - Subject string `json:"subject"` // cert subject CN - Issuer string `json:"issuer"` // cert issuer CN - NotBefore time.Time `json:"not_before"` - NotAfter time.Time `json:"not_after"` - SerialNumber string `json:"serial_number"` - ResponseTimeMs int `json:"response_time_ms"` - Error string `json:"error,omitempty"` + Address string `json:"address"` + Success bool `json:"success"` + Fingerprint string `json:"fingerprint"` // SHA-256 hex fingerprint of leaf cert + TLSVersion string `json:"tls_version"` // e.g. "TLS 1.3" + CipherSuite string `json:"cipher_suite"` // e.g. "TLS_AES_128_GCM_SHA256" + Subject string `json:"subject"` // cert subject CN + Issuer string `json:"issuer"` // cert issuer CN + NotBefore time.Time `json:"not_before"` + NotAfter time.Time `json:"not_after"` + SerialNumber string `json:"serial_number"` + ResponseTimeMs int `json:"response_time_ms"` + Error string `json:"error,omitempty"` } // ProbeTLS connects to a TLS endpoint, performs a handshake, and extracts certificate metadata. diff --git a/internal/validation/command_test.go b/internal/validation/command_test.go index be30225..dad7aa8 100644 --- a/internal/validation/command_test.go +++ b/internal/validation/command_test.go @@ -525,4 +525,3 @@ func TestSanitizeForShell(t *testing.T) { }) } } -