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
+3
View File
@@ -0,0 +1,3 @@
-- Rollback Migration 000006: Filesystem Certificate Discovery
DROP TABLE IF EXISTS discovered_certificates;
DROP TABLE IF EXISTS discovery_scans;
+59
View File
@@ -0,0 +1,59 @@
-- Migration 000006: Filesystem Certificate Discovery
-- Agents scan configured directories for existing certificates and report to the control plane.
-- The control plane deduplicates by SHA-256 fingerprint and stores discovery metadata.
-- Discovery scans track each scan run by an agent
CREATE TABLE IF NOT EXISTS discovery_scans (
id TEXT PRIMARY KEY,
agent_id TEXT NOT NULL REFERENCES agents(id),
directories TEXT[] NOT NULL,
certificates_found INTEGER NOT NULL DEFAULT 0,
certificates_new INTEGER NOT NULL DEFAULT 0,
errors_count INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER NOT NULL DEFAULT 0,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_discovery_scans_agent_id ON discovery_scans(agent_id);
CREATE INDEX IF NOT EXISTS idx_discovery_scans_started_at ON discovery_scans(started_at DESC);
-- Discovered certificates store certs found on agent filesystems
CREATE TABLE IF NOT EXISTS discovered_certificates (
id TEXT PRIMARY KEY,
fingerprint_sha256 TEXT NOT NULL,
common_name TEXT NOT NULL DEFAULT '',
sans TEXT[] DEFAULT '{}',
serial_number TEXT NOT NULL DEFAULT '',
issuer_dn TEXT NOT NULL DEFAULT '',
subject_dn TEXT NOT NULL DEFAULT '',
not_before TIMESTAMPTZ,
not_after TIMESTAMPTZ,
key_algorithm TEXT NOT NULL DEFAULT '',
key_size INTEGER NOT NULL DEFAULT 0,
is_ca BOOLEAN NOT NULL DEFAULT FALSE,
pem_data TEXT NOT NULL DEFAULT '',
source_path TEXT NOT NULL DEFAULT '',
source_format TEXT NOT NULL DEFAULT 'PEM',
agent_id TEXT NOT NULL REFERENCES agents(id),
discovery_scan_id TEXT REFERENCES discovery_scans(id),
managed_certificate_id TEXT REFERENCES managed_certificates(id),
status TEXT NOT NULL DEFAULT 'Unmanaged',
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
dismissed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Unique constraint: same fingerprint on same agent at same path
CREATE UNIQUE INDEX IF NOT EXISTS idx_discovered_certs_fingerprint_agent_path
ON discovered_certificates(fingerprint_sha256, agent_id, source_path);
-- Performance indexes
CREATE INDEX IF NOT EXISTS idx_discovered_certs_agent_id ON discovered_certificates(agent_id);
CREATE INDEX IF NOT EXISTS idx_discovered_certs_status ON discovered_certificates(status);
CREATE INDEX IF NOT EXISTS idx_discovered_certs_fingerprint ON discovered_certificates(fingerprint_sha256);
CREATE INDEX IF NOT EXISTS idx_discovered_certs_not_after ON discovered_certificates(not_after);
CREATE INDEX IF NOT EXISTS idx_discovered_certs_managed_id ON discovered_certificates(managed_certificate_id)
WHERE managed_certificate_id IS NOT NULL;