mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:21:30 +00:00
chore(fmt): repo-wide gofmt -w sweep — close drift surfaced by ci-pipeline-cleanup Phase 4
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 0f205a8) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.
Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.
The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
This commit is contained in:
@@ -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"},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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{}
|
||||
}{
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -249,4 +249,3 @@ func signJWS(key *ecdsa.PrivateKey, kid, nonce, targetURL string, payload []byte
|
||||
|
||||
return json.Marshal(jws)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -643,7 +643,7 @@ func generateTestCSR(cn string) (*x509.CertificateRequest, string, error) {
|
||||
}
|
||||
|
||||
csrTemplate := x509.CertificateRequest{
|
||||
Subject: subject,
|
||||
Subject: subject,
|
||||
DNSNames: []string{cn, "www." + cn},
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = <ca-url>/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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -310,4 +310,3 @@ func (c *Connector) ValidateDeployment(ctx context.Context, request target.Valid
|
||||
|
||||
// Ensure Connector implements target.Connector.
|
||||
var _ target.Connector = (*Connector)(nil)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
+17
-17
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
|
||||
func TestRegisterAgent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
agentRepo := &mockAgentRepo{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+14
-14
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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{}}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user