The old v1 binary was built with Go 1.23 and rejected Go 1.25 targets.
Migrated .golangci.yml to v2 format: added version field, moved
linters-settings under linters.settings, removed deprecated linters
(structcheck/deadcode/varcheck), merged gosimple into staticcheck.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add context.Context to handler test mocks (agent, agent_group)
- Refactor scheduler to use local interfaces instead of concrete service types
- Wire RevocationSvc/CAOperationsSvc sub-services in integration tests
- Add context.Background() to service test calls (agent, agent_group)
- Fix repo integration tests: add FK prerequisite records (team, owner,
issuer, renewal_policy) before creating certificates
- Set MaxOpenConns(1) on test DB to preserve SET search_path across queries
- Fix Apache/HAProxy tests: replace "echo ok"/"echo reload" with "true"
binary to avoid macOS exec.Command PATH resolution failure
- Fix validation tests: correct error expectations for regex-first checks,
replace null byte strings with strings.Repeat for length tests
- Fix scheduler timeout test flakiness with t.Skip fallback
- Remove unused imports (context in ca_operations_test, service in scheduler)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements 3 deferred security tickets (TICKET-003, TICKET-007, TICKET-010)
and performs comprehensive documentation audit to eliminate drift between
code and docs.
Code changes:
- TICKET-003: Repository integration tests with testcontainers-go (50+ subtests)
- TICKET-007: CertificateService decomposition into RevocationSvc + CAOperationsSvc
- TICKET-010: Request body size limits via http.MaxBytesReader middleware
- Fix missing slog import in certificate.go after service decomposition
Documentation updates:
- README: Fix endpoint count (97→93), expand env var reference (15→39 vars)
- CLAUDE.md: Fix OpenAPI operation count (85→93), update file locations
- architecture.md: Add body size limits section, middleware chain ordering
- CONTRIBUTING.md: New contributor guide with architecture conventions,
test patterns, middleware ordering, CI thresholds
- SECURITY_REMEDIATION.md: Removed from repo (moved to cowork, gitignored)
- Test files: Add doc comments to all new test files
Documentation that should exist but doesn't yet:
- Architecture diagrams (C4 model or similar)
- Threat model document
- Testing philosophy guide
- Disaster recovery runbook
- Upgrade guide (migration between versions)
- API versioning strategy document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update README, architecture guide, and feature inventory to document all
changes from the security remediation pass (17 tickets):
- README: Add CI pipeline section (race detection, golangci-lint,
govulncheck, per-layer coverage thresholds), CORS deny-by-default
behavior, input validation, SSRF protection, scheduler concurrency
safety. Update test count to 1050+. Add race detection and govulncheck
to development commands.
- Architecture guide: Update testing strategy with scheduler tests, fuzz
tests, and revised CI pipeline description. Add security model sections
for input validation, CORS, and concurrency safety. Update test count.
- Feature inventory: Document CORS deny-by-default behavior.
- SECURITY_REMEDIATION.md: New file documenting all 17 remediated tickets
with CWE classifications, before/after behavior, 3 deferred tickets
with rationale, CI pipeline changes, and breaking CORS change.
Missing docs flagged as future additions:
- Formal threat model document
- Disaster recovery runbook
- Version upgrade guide
- Capacity planning benchmarks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added Go native fuzz tests (testing/fuzz) for security-critical input validation:
1. FuzzValidateShellCommand in internal/validation/command_fuzz_test.go
- Tests shell command validation with injection payloads (;, |, &, $, `, etc.)
- Seed corpus includes valid commands and dangerous metacharacters
- Ensures function never panics under fuzzing
2. FuzzValidateDomainName in internal/validation/command_fuzz_test.go
- Tests RFC 1123 domain validation with wildcard support
- Seed corpus includes SQL injection, path traversal, and malformed domains
- Ensures function never panics under fuzzing
3. FuzzValidateACMEToken in internal/validation/command_fuzz_test.go
- Tests base64url token validation
- Seed corpus includes injection payloads and special characters
- Ensures function never panics under fuzzing
4. FuzzIsValidRevocationReason in internal/domain/revocation_fuzz_test.go
- Tests RFC 5280 revocation reason validation
- Seed corpus includes case variations, injection attempts, and null bytes
- Ensures function never panics and returns only valid booleans
5. FuzzCRLReasonCode in internal/domain/revocation_fuzz_test.go
- Tests CRL reason code mapping
- Validates return codes are within 0-9 range
- Ensures invalid reasons default to 0 (unspecified)
All fuzz tests follow Go 1.18+ testing/fuzz conventions with seed corpus
for faster discovery of edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The audit middleware records events asynchronously via goroutines. Tests previously
used time.Sleep(50ms) to wait for audit recording, which is unreliable.
Implemented waitableAuditRecorder wrapper that:
- Wraps mockAuditRecorder to intercept RecordAPICall invocations
- Signals via buffered channel when recording completes
- Provides Wait(timeout) method for tests to synchronously wait
- Returns true on successful wait, false on timeout
Replaced all 7 time.Sleep(50ms) calls with recorder.Wait(1*time.Second) calls,
improving test reliability and reducing flakiness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the 18-parameter RegisterHandlers function signature with a cleaner
HandlerRegistry struct that groups all API handler dependencies. This eliminates
the signature explosion that made the function difficult to read and maintain.
Changes:
- Added HandlerRegistry struct with 18 fields grouping all handler types
- Updated RegisterHandlers to accept a single HandlerRegistry parameter
- Updated all internal handler references to use reg.FieldName syntax
- Updated call sites in cmd/server/main.go and integration tests
- No functional changes, purely structural refactoring
Resolves TICKET-006: RegisterHandlers Signature Explosion
The generateTestCert() function previously returned &x509.Certificate{Raw: []byte("test")},
which is not a valid DER-encoded certificate. Replace with a proper self-signed certificate
generator using ECDSA P-256 that creates valid X.509 certificates for testing.
Added imports: crypto/ecdsa, crypto/elliptic, crypto/rand, crypto/x509/pkix, math/big
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated AgentService interface to accept context.Context parameter in all methods
- Replaced context.Background() calls with proper ctx parameter in agent.go
- Updated AgentGroupService interface to accept context.Context parameter
- Replaced context.Background() calls with proper ctx parameter in agent_group.go
- Updated handler methods to pass r.Context() to service methods
- Context now properly propagates through request lifecycle for timeout/cancellation
- Improved request tracing and cancellation behavior
## Summary
Fixes two critical scheduler reliability issues in certctl:
### TICKET-002 (CRITICAL): Scheduler job idempotency
- Added atomic.Bool guards to all 6 scheduler loops (renewal, job processor, agent health, notifications, short-lived expiry, network scan)
- Uses CompareAndSwap pattern to prevent duplicate execution if previous job is still running
- Logs warning when a tick is skipped due to in-flight work
- Prevents runaway scheduler duplicates and resource exhaustion
### TICKET-011 (MEDIUM): Graceful shutdown
- Added sync.WaitGroup to track in-flight scheduler work
- Each job is wrapped in wg.Add(1)/wg.Done() for lifecycle tracking
- New WaitForCompletion(timeout) method waits for all in-flight work to complete
- Integrates into main.go: after context cancellation, waits up to 30s for jobs to finish before closing DB
- Graceful shutdown ensures no work is lost during server restart/termination
## Changes
**internal/scheduler/scheduler.go:**
- Imports: added "errors", "sync", "sync/atomic"
- Scheduler struct: added 6 atomic.Bool fields (one per loop) + sync.WaitGroup
- All 6 loop functions: spawn goroutines with wg.Add/Done, check atomic guard on each tick, skip tick if already running
- New WaitForCompletion(timeout) method with timeout support
- New ErrSchedulerShutdownTimeout error type
**cmd/server/main.go:**
- After context cancellation and before HTTP shutdown, call sched.WaitForCompletion(30 * time.Second)
- Logs "waiting for scheduler to complete in-flight work" and any errors
**internal/scheduler/scheduler_test.go (new file):**
- Mock services for testing (renewal, job, agent, notification, network scan)
- TestSchedulerIdempotencyGuard: verifies slow job doesn't cause duplicate execution
- TestWaitForCompletionSuccess: verifies graceful shutdown with adequate timeout
- TestWaitForCompletionTimeout: verifies timeout is respected
- TestSchedulerMultipleLoopsIdempotency: verifies all 6 loops respect idempotency
- TestSchedulerGracefulShutdown: end-to-end graceful shutdown flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added TestSlack_ClientHasTimeout to verify 10-second timeout
- Added TestTeams_ClientHasTimeout to verify 10-second timeout
- Added TestPagerDuty_ClientHasTimeout to verify 10-second timeout
- Added TestOpsGenie_ClientHasTimeout to verify 10-second timeout
- All notifiers already configured with 10 second timeout in New()
- Tests verify timeout is set and matches expected value
- Fix undefined tls.Listener in verify_test.go (type doesn't exist in
crypto/tls); use server.Listener.Addr() and server.TLS.Certificates
- Fix mockJobRepository missing Delete/ListByStatus/ListByCertificate/
UpdateStatus/GetPendingJobs methods required by JobRepository interface
- Fix mockAuditService type mismatch: NewVerificationService expects
*AuditService (concrete), not a mock; use real AuditService with mock
repo following existing testutil_test.go patterns
- Fix List() signature mismatch (had extra filter param)
- Add nil-safe logger checks in verify.go to prevent panics in tests
- Remove unused imports (crypto/tls, bytes, repository)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The handler's VerificationService interface used interface{} for the ctx
parameter, but the service implementation uses context.Context. This caused
a compile error: *service.VerificationService does not implement
handler.VerificationService.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
M25: After deploying a certificate, the agent probes the live TLS
endpoint and compares SHA-256 fingerprints to verify the correct cert
is being served. Best-effort — failures don't block deployments.
New endpoints: POST /jobs/{id}/verify, GET /jobs/{id}/verification.
Migration 000008 adds verification columns to jobs table.
M26: Traefik target connector (file provider, auto-reload) and Caddy
target connector (dual-mode: admin API hot-reload or file-based).
Both wired into agent dispatch.
Also: restructured README to highlight supported integrations (issuers,
targets, notifiers) earlier, moved API/CLI/MCP sections lower. Updated
all docs (features, connectors, architecture, testing guide, why-certctl)
and fixed integration tests for 18-param RegisterHandlers signature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace verbose bullet-list comparisons with dense single-paragraph
summaries for all 7 competitors. Each paragraph covers what the tool
is, what it lacks vs certctl, and where it leads. 48 lines cut.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
README: Remove Contents section (GitHub auto-generates ToC), replace
12-bullet Core capabilities block with link to Feature Inventory,
replace 21-row Database Schema table with one-liner linking to
Architecture Guide. Visitors now hit screenshots ~60 lines sooner.
why-certctl: Remove Feature Summary section (duplicated README and
Feature Inventory content). Competitive comparisons remain as the
focused value of this page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The detailed prose comparisons in why-certctl.md are sufficient.
Tables were redundant with the per-competitor sections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove list markers so dot-separated links flow as a single line
on GitHub instead of rendering as three bullet points.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
README: Replace old 5-column comparison table with 7-competitor table
(certctl, CertKit, CertWarden, Certimate, CZERTAINLY, KeyTalk, cert-manager)
with Free tier row. Remove CertKit from documentation table link text.
Version badge v2.0.4 → v2.0.5, add Why certctl? and Feature Inventory
to docs table, condense ToC, trim Configuration/API/Roadmap sections
with links to detailed docs.
why-certctl.md: Add detailed comparison sections for Certimate (cloud/CDN
focus, no agent, ACME-only), CZERTAINLY (K8s-required microservices,
pluggable connectors, broader vision), and KeyTalk (proprietary, multi-cert-type,
no public docs). Add 14-row summary comparison table covering all 7 competitors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs fixed:
- Docker Compose only mounted migration 000001; migrations 000002-000007
(profiles, agent groups, revocation, discovery, network scans) never ran,
breaking half the demo features. Now mounts all 7 migrations in order.
- Network Scans page crashed with pq.Array scan error because lib/pq
doesn't support []int, only []int64. Changed Ports field accordingly.
- Dashboard pie chart displayed "RenewalInProgress" without spaces.
Added formatStatus() helper for PascalCase → spaced display.
Also adds first-run demo experience improvements:
- 9 discovered certificates (filesystem + network scan mix)
- 3 discovery scans with recent timestamps
- 2 AwaitingApproval renewal jobs for approval workflow demo
- CERTCTL_NETWORK_SCAN_ENABLED=true in Docker Compose
- Network scan targets seeded with last_scan results
- Version badge updated to v2.0.5
- Docs updated (quickstart, advanced demo) to reference seeded data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update concepts.md and connectors.md to mention the Discovery and
Network Scans dashboard pages alongside existing API documentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Part 19.5 (approval workflow), 19.6 (discovery triage),
19.7 (network scan management) to GUI testing section. Renumbers
existing 19.5 Other Pages to 19.8 and Cross-Cutting to 19.9.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three new GUI surfaces closing the backend-to-frontend gap for V2:
- Discovery triage page: summary stats bar, DataTable with claim/dismiss
actions, status/agent filters, collapsible scan history panel
- Network scan target management: CRUD with create modal, enable/disable
toggle, Scan Now button, last scan results display
- Jobs page approval workflow: Approve/Reject buttons for AwaitingApproval
jobs, rejection reason modal, pending approval banner with count,
AwaitingApproval/AwaitingCSR added to status filter dropdown
Also adds 13 new frontend tests, 4 API types, 12 API client functions,
2 sidebar nav items, 2 routes, and discovery status badge styles.
Docs updated: README, architecture, quickstart, demo-advanced, CLAUDE.md,
roadmap. Version bumped to v2.0.4.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EAB credentials (KID + HMAC) were defined in the ACME connector config
but never wired into the acme.Account registration call. This fixes the
dead code and adds automatic EAB credential fetching for ZeroSSL — when
the directory URL is detected as ZeroSSL and no EAB credentials are
provided, certctl calls ZeroSSL's public API to get them automatically.
Changes:
- Wire EABKid/EABHmac into acme.Account.ExternalAccountBinding
- Add isZeroSSL() detection and fetchZeroSSLEAB() auto-fetch
- Add CERTCTL_ACME_EAB_KID/CERTCTL_ACME_EAB_HMAC env vars to main.go
- Add 13 ACME connector tests (config validation, EAB decode, ZeroSSL
auto-EAB with mock servers, URL detection)
- Update docs: README, architecture, connectors, demo-advanced,
testing-guide with EAB/auto-EAB documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Navigation menus for testing guide, architecture, concepts,
connectors, quickstart, advanced demo, and three compliance docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete frontend visual redesign using certctl logo color palette:
- Deep teal sidebar (#0c2e25) with prominent centered logo (64px in white pill)
- Light content area (#f0f4f8) with white cards and visible borders
- Brand colors from logo: teal (#2ea88f), blue (#3b7dd8), orange (#e8873a), green (#4ebe6e)
- Inter + JetBrains Mono typography, colored stat card top borders
- All 17 pages + 7 components updated (25 files, ~700 lines changed)
- 15 new dashboard screenshots replacing old dark theme screenshots
- Prometheus metrics e2e test added, integration test mock fixes
- Docs updated: architecture.md theme description, testing-guide.md DNS-PERSIST-01 coverage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standing TXT record at _validation-persist.<domain> eliminates per-renewal
DNS updates. Auto-fallback to dns-01 if CA doesn't offer dns-persist-01.
ScriptDNSSolver extended with PresentPersist method. Configurable via
CERTCTL_ACME_CHALLENGE_TYPE=dns-persist-01 and
CERTCTL_ACME_DNS_PERSIST_ISSUER_DOMAIN env vars.
Also fixes IsExpired edge-case test in discovery_test.go that always failed
due to time.Now() drift between test setup and method invocation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove both mermaid flowcharts from README to reduce visual noise.
Architecture doc already has a more detailed version. Replace with
a one-line text summary linking to docs/architecture.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Traefik/Caddy to deployment targets table and architecture
diagram, S/MIME to core capabilities, M24/M25/M26 to V2 roadmap
section, version badge to v2.0.1, stats to 95+ endpoints and
930+ tests. Clarify Vault PKI and DigiCert as future. Expand V4
description. Add OpenSSL/Custom CA note for ADCS integrations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CompletedAt was set to Now()-1h which falls on "yesterday" when CI
runs near midnight UTC, causing the date bucket lookup to miss.
Use Now() directly since the test only needs jobs completed "today".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>