Production hardening II Phase 7 — typify the cert-export audit
emission. The pre-Phase-7 audit log carried inline strings
("export_pem" / "export_pkcs12"); this commit adds typed
constants alongside via the split-emit pattern so operators get
both back-compat with existing log analysers AND a stable typed
grep target.
NEW internal/service/export_audit_actions.go:
- AuditActionCertExportPEM = "cert_export_pem"
- AuditActionCertExportPEMWithKey = "cert_export_pem_with_key"
(reserved for future bundle that adds key-bearing export; not
emitted in V2)
- AuditActionCertExportPKCS12 = "cert_export_pkcs12"
- AuditActionCertExportFailed = "cert_export_failed"
- PKCS12CipherModernAES256 = "AES-256-CBC-PBE2-SHA256" pinned
string for the cipher detail (drift catches a future go-pkcs12
default change)
Detail enrichment on both emission sites:
- has_private_key (bool, V2 always false — cert-only export is
the only V2 path; key-bearing export deferred to future bundle)
- actor_kind ("user")
- cipher (PKCS12 only — pinned to PKCS12CipherModernAES256)
Split-emit pattern: each export emits BOTH the legacy bare action
code AND the typed constant. Mirrors est.go::processEnrollment which
emits both "est_simple_enroll" + "est_simple_enroll_success".
Existing audit-log analysers that match by exact string "export_pem"
keep working; new operator alerts can target the typed constant.
Pre-commit verification: go build ./... clean; go test -short
-count=1 green for service/.
Root cause: certificate_versions.csr_pem is nullable in the schema but
Go code scanned it into a plain string. Used sql.NullString in
ListVersions and GetLatestVersion to handle NULL values correctly.
Also includes: partial update fetch-merge-update pattern to prevent FK
violations, nil directory guard in discovery service, diagnostic slog
logging in handlers, export handler 422 for unparseable PEM, OpenAPI
spec corrections, MCP tool description improvements, and test fixes.
Rewrites the Release Sign-Off section in testing-guide.md to individual
test-level granularity (320 rows) with smoke test results audited and
checked off (121 pass, 5 skip, 194 manual remaining).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add certificate export in PEM (JSON or file download) and PKCS#12 formats.
Private keys are never included — they stay on agents. Add EKU-aware
issuance threading profile EKUs (serverAuth, clientAuth, codeSigning,
emailProtection, timeStamping) through the full issuance pipeline. Fix
agent CSR SAN splitting for email addresses, adaptive KeyUsage flags for
S/MIME vs TLS, and a pre-existing generateID collision bug in deployment
job creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>