feat: M18b Filesystem Certificate Discovery — agent scanning, server dedup, triage API

Agent-side:
- Filesystem scanner walks configured directories (CERTCTL_DISCOVERY_DIRS)
- Parses PEM (.pem, .crt, .cer, .cert) and DER (.der) certificate files
- Extracts CN, SANs, serial, issuer/subject DN, validity, key info, SHA-256 fingerprint
- Reports discoveries to control plane on startup + every 6 hours
- Skips files >1MB and private key files

Server-side:
- Migration 000006: discovered_certificates + discovery_scans tables
- Domain model: DiscoveredCertificate, DiscoveryScan, DiscoveryReport
- Three triage states: Unmanaged, Managed (claimed), Dismissed
- Repository with upsert dedup (fingerprint + agent + path)
- Service layer: process reports, claim, dismiss, list, summary
- 7 new API endpoints (84 total):
  POST /agents/{id}/discoveries, GET /discovered-certificates,
  GET /discovered-certificates/{id}, POST .../claim, POST .../dismiss,
  GET /discovery-scans, GET /discovery-summary
- Audit trail: scan_completed, cert_claimed, cert_dismissed events

Tests: 28 new test functions (domain, handler, service layers)
Docs: README, quickstart, demo-guide, demo-advanced, architecture,
      concepts, connectors, features.md all updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shankar0123
2026-03-24 00:25:00 -04:00
parent 8768a7b3ef
commit 667a30870d
23 changed files with 2916 additions and 24 deletions
+33
View File
@@ -205,6 +205,39 @@ type AgentGroupRepository interface {
RemoveMember(ctx context.Context, groupID, agentID string) error
}
// DiscoveryRepository defines operations for managing certificate discovery.
type DiscoveryRepository interface {
// CreateScan stores a new discovery scan record.
CreateScan(ctx context.Context, scan *domain.DiscoveryScan) error
// GetScan retrieves a discovery scan by ID.
GetScan(ctx context.Context, id string) (*domain.DiscoveryScan, error)
// ListScans returns discovery scans, optionally filtered by agent ID.
ListScans(ctx context.Context, agentID string, page, perPage int) ([]*domain.DiscoveryScan, int, error)
// CreateDiscovered stores a new discovered certificate (upserts by fingerprint+agent+path).
// Returns true if the certificate was newly inserted (not just updated).
CreateDiscovered(ctx context.Context, cert *domain.DiscoveredCertificate) (bool, error)
// GetDiscovered retrieves a discovered certificate by ID.
GetDiscovered(ctx context.Context, id string) (*domain.DiscoveredCertificate, error)
// ListDiscovered returns discovered certificates matching the filter.
ListDiscovered(ctx context.Context, filter *DiscoveryFilter) ([]*domain.DiscoveredCertificate, int, error)
// UpdateDiscoveredStatus updates the status and optional managed certificate link.
UpdateDiscoveredStatus(ctx context.Context, id string, status domain.DiscoveryStatus, managedCertID string) error
// GetByFingerprint retrieves discovered certificates by SHA-256 fingerprint.
GetByFingerprint(ctx context.Context, fingerprint string) ([]*domain.DiscoveredCertificate, error)
// CountByStatus returns counts of discovered certificates grouped by status.
CountByStatus(ctx context.Context) (map[string]int, error)
}
// DiscoveryFilter defines filters for listing discovered certificates.
type DiscoveryFilter struct {
AgentID string
Status string
IsExpired bool
IsCA bool
Page int
PerPage int
}
// OwnerRepository defines operations for managing certificate owners.
type OwnerRepository interface {
// List returns all owners.