Commit Graph

15 Commits

Author SHA1 Message Date
shankar0123 0594631e6a gui/cert-detail: revocation endpoints panel (CRL/OCSP) — Phase 5
CertificateDetailPage now surfaces a Revocation Endpoints card showing
the standards-compliant /.well-known/pki/crl/{issuer_id} CRL distribution
point (RFC 5280 §4.2.1.13) and /.well-known/pki/ocsp/{issuer_id} OCSP
responder URL (RFC 6960 §A.1) for relying parties that don't already know
certctl's well-known scheme.

Two action buttons exercise the same network path the issued leaves'
AIA/CDP extensions advertise, so an operator can confirm 'did the
backend Phases 1-4 actually wire end-to-end?' without curl:
  * 'Test CRL fetch'   — fetchCRL(issuer_id) helper, surfaces byte count
  * 'Check OCSP status' — getOCSPStatus(issuer_id, serial_hex) helper

Admin-only cache-age badge: when useAuth().admin is true the panel pulls
GET /api/v1/admin/crl/cache (M-008 admin-gated handler) and shows
'Cache fresh · 2m ago' / 'Cache stale' / 'Not yet generated' next to
the heading. Non-admin callers don't trigger the fetch (gated client-side
on enabled flag, server-side on middleware.IsAdmin) so the badge cannot
leak generation cadence.

Test coverage in CertificateDetailPage.test.tsx pins:
  1. CRL + OCSP URLs render with issuer_id substituted
  2. Test CRL fetch button calls fetchCRL with the issuer_id and renders
     the byte-count success message
  3. Check OCSP status button calls getOCSPStatus with (issuer_id, serial)
     and renders the DER byte-count
  4. Admin badge stays HIDDEN (and getAdminCRLCache is NEVER called) when
     useAuth().admin is false — pins the no-info-leak invariant

P-1 closure docblock + CI guardrail (.github/workflows/ci.yml) updated
to remove getOCSPStatus from the documented-orphan list since it now
has a real consumer.

types.ts: CRLCacheRow / CRLCacheEvent / CRLCacheResponse mirrors of the
backend admin handler payload (admin_crl_cache.go).

client.ts: fetchCRL + getAdminCRLCache helpers; getOCSPStatus already
existed and is now an active consumer.

Tests: 6/6 in CertificateDetailPage.test.tsx, 150/150 across api+page
suite. tsc --noEmit clean.
2026-04-29 02:58:39 +00:00
shankar0123 213b464d95 M-029 Pass 1 batch 6 (FINAL): migrate 2 five-mutation pages — Pass 1 complete
Drains the last 10 useMutation sites (10 -> 0). Pass 1 is now COMPLETE:

every legacy useMutation site in src/pages and src/components has been

migrated to useTrackedMutation with explicit invalidates contract. The only

remaining useMutation reference in the codebase is inside useTrackedMutation.ts

itself (the wrapper).

Pages migrated:

  - CertificateDetailPage.tsx  5 mutations across 2 components:

                                InlinePolicyEditor.saveMutation invalidates

                                [['certificate', certId]];

                                main page renew/deploy/archive/revoke invalidate

                                various combinations of [['certificate', id]]

                                and [['certificates']].

                                (queryClient + useQueryClient dropped from both)

  - OnboardingWizard.tsx        5 mutations across 4 components:

                                Issuer step create/test invalidates [['issuers']]

                                (test refreshes last_tested_at server-side);

                                CreateTeamModalInline.create invalidates [['teams']];

                                CreateOwnerModalInline.create invalidates [['owners']];

                                CertificateStep.create invalidates

                                [['certificates'], ['dashboard-summary']].

                                (queryClient + useQueryClient dropped from all 4)

Verification:

  legacy useMutation calls   10 -> 0 (-10) — Pass 1 COMPLETE

  useTrackedMutation count   46 -> 61 (+15; some 5-mutation pages collapse

                                two invalidate-pairs into one array literal,

                                hence net is greater than the +10 removal)

Pass 1 totals: 56 useMutation sites -> 0; 0 useTrackedMutation -> 61.

Total work in Pass 1: 6 batches across 21 page files merged --no-ff to master.
2026-04-27 02:54:28 +00:00
shankar0123 9dc0742e77 fix(web): close StatusBadge enum drift + Certificate TS phantom fields (D-1 master)
Five audit findings, all category cat-d or cat-f, all rooted in two
frontend files. The dashboard silently lied:

  cat-d-359e92c20cbf [P1, primary] — Agent: 'Stale' dead key + 'Degraded'
                                     neutral fallthrough
  cat-d-9f4c8e4a91f1 [P2]          — Notification: 'dead' missing
  cat-d-1447e04732e7 [P3]          — Cert: 'PendingIssuance' dead key
  cat-f-cert_detail_page_key_render_fallback [P2] — render-site reads
                                                    cert.key_algorithm directly
  cat-f-ae0d06b6588f [P2]          — Certificate TS phantom fields (root cause)

Pre-D-1, agents in the only Go AgentStatus that means 'needs operator
attention' (Degraded) rendered as default neutral grey because StatusBadge
mapped 'Stale' (a key Go has never emitted) to yellow. Dead-letter
notifications visually equated with 'read' (operator-acknowledged). The
Certificate badge map carried a 'PendingIssuance' key no Go enum emits.
CertificateDetailPage's Key Algorithm and Key Size rows always rendered
'—' even when the data was a single fetch away — the lookup went through
cert.key_algorithm / cert.key_size directly, both phantom Certificate TS
fields. Trim the TS type so the missing-data case is explicit; fix the
render site to use latestVersion?.field; pin the contract with a 38-case
Vitest property test that walks every Go enum.

StatusBadge (web/src/components/StatusBadge.tsx)
- Drop 'Stale' (Agent dead key) + 'PendingIssuance' (Cert dead key).
- Add 'Degraded' (Agent → badge-warning) + 'dead' (Notification → badge-danger).
- Add leading docblock naming Go-side source-of-truth file for every
  status family and pointing at the property test as regression vector.

Property test (web/src/components/StatusBadge.test.tsx — 38 cases)
- Iterates every Go-emitted enum value (AgentStatus, CertificateStatus,
  JobStatus, NotificationStatus, DiscoveryStatus, HealthStatus) plus the
  two frontend-synthesized Enabled/Disabled labels, asserts every value
  gets a non-default class (or an explicit 'badge badge-neutral' for the
  five intentionally-neutral terminal values: Archived, Cancelled,
  Dismissed, read, unknown).
- Negative assertions: 'Stale' and 'PendingIssuance' must fall through
  to the dictionary default — re-adding either key surfaces here.
- Specific UX-correctness assertions: 'dead' → badge-danger,
  'Degraded' → badge-warning.
- Unknown-status fallthrough preserves label text.

Certificate TS trim (web/src/api/types.ts)
- Drop serial_number?, fingerprint_sha256?, key_algorithm?, key_size?,
  issued_at? from Certificate. Go's ManagedCertificate has never carried
  these — they live on CertificateVersion. Post-trim a cert.X access for
  any of the five fields is a TS compile error.
- Leading docblock cross-references the closure rationale and the
  latestVersion fallback pattern.

Render-site fix (web/src/pages/CertificateDetailPage.tsx)
- Key Algorithm / Key Size rows now read latestVersion?.key_algorithm /
  latestVersion?.key_size, mirroring the existing latestVersion fallback
  used a few lines above for serial_number / fingerprint_sha256.
- The same edit also tightened the serial / fingerprint / issued_at
  derivations to drop the now-impossible 'cert.X || latestVersion?.X'
  cert-side leg (cert.serial_number is a TS error post-trim).

Type-test regression (web/src/api/types.test.ts)
- Certificate literal construction pinned post-trim — adding any of the
  five fields back makes the literal an excess-property TS error.
- Sibling CertificateVersion literal pinning the trimmed fields still
  live on the version envelope (so the CertificateDetailPage fallback
  path can't break).

OpenAPI (api/openapi.yaml)
- ManagedCertificate schema unchanged — was already correct (no phantom
  fields). Added a leading comment cross-referencing the D-5 closure for
  future readers.

CI guardrail (.github/workflows/ci.yml)
- 'Forbidden StatusBadge dead-key + Certificate phantom-field regression
  guard (D-1)'. Two grep blocks: catches Stale/PendingIssuance map
  literals in StatusBadge.tsx; uses an awk-scoped window over the
  'export interface Certificate {' block in types.ts to catch the five
  phantom fields reappearing while explicitly excluding CertificateVersion
  (which legitimately carries them). Comments + test files exempt.

Verification
- Backend build/vet/test -short -race all clean across handler/router/
  middleware packages.
- Frontend tsc --noEmit clean.
- Vitest 256 → 296 tests (+40: 38 from new StatusBadge test, 2 from D-5
  Certificate trim regression in types.test.ts).
- OpenAPI YAML parses (87 paths).
- Both CI guardrail patterns clear on the post-fix tree; both fire
  against synthetic regression patterns (re-add Stale → fires; re-add
  serial_number? to Certificate → fires).

Out of scope (deferred)
- diff-05x06-* type drifts for Agent/DeploymentTarget/Notification/
  DiscoveredCertificate/Issuer TS interfaces. Per-type field-by-field
  Go ↔ TS diff is codegen-shaped, not edit-shaped — warrants its own
  D-2 master prompt. Noted in CHANGELOG follow-ups section.
2026-04-25 13:52:54 +00:00
shankar0123 9834b4e4a4 G-1: renewal-policies API + frontend FK-drift fix
Three frontend call sites (OnboardingWizard.tsx:603, CertificatesPage.tsx:52,
CertificateDetailPage.tsx:169) populated the renewal_policy_id dropdown from
getPolicies() — the compliance-rule endpoint returning pol-* IDs — which
violated the FK managed_certificates.renewal_policy_id REFERENCES
renewal_policies(id) ON DELETE RESTRICT. Create would fail pg 23503 at insert.

Backend (new):
- RenewalPolicyRepository CRUD + ListAll/ExistsByID (pg 23503 → ErrRenewalPolicyInUse
  → HTTP 409; pg 23505 → ErrRenewalPolicyDuplicateName → HTTP 409)
- RenewalPolicyService with repo-only constructor. Service sentinels
  var-alias the repo sentinels so errors.Is walks across layers.
- RenewalPolicyHandler with validation bounds: name 1–255;
  renewal_window_days [1,365] default 30; max_retries [0,10] not defaulted;
  retry_interval_seconds [60,86400] default 3600; alert_thresholds_days
  [0,365] default [30,14,7,0]. Auto-generated IDs rp-<slug(name)>.
- Router registers 5 routes under /api/v1/renewal-policies[/{id}].

Frontend:
- CertificatesPage/CertificateDetailPage/OnboardingWizard now call
  getRenewalPolicies() and render rp-* IDs.
- client.ts adds getRenewalPolicies/createRenewalPolicy/updateRenewalPolicy/
  deleteRenewalPolicy. types.ts adds the RenewalPolicy shape.

OpenAPI: RenewalPolicies tag + 5 operations + 3 schemas (RenewalPolicy,
RenewalPolicyCreateRequest, RenewalPolicyUpdateRequest). 409 responses
on create/update duplicate-name and delete FK-in-use.

No migration — renewal_policies table already exists from the initial
schema (000001).

Tests:
- internal/service/renewal_policy_test.go: CRUD + validation + sentinel
  error wrapping.
- internal/api/handler/renewal_policy_handler_test.go: handler endpoint
  contracts including 400/404/409.
- web/src/api/client.test.ts: 4 subtests covering the 4 new API functions.

Phase 3 gates all green: go vet, build, short tests, race tests (service/
handler/router/scheduler), staticcheck (G-1 packages), govulncheck (0
reachable), coverage (service 69.7%, handler 79.0%, domain 86.9%,
middleware 80.6% — all above thresholds), tsc, vitest (256 passed),
vite build, OpenAPI structural validation.
2026-04-20 18:53:01 +00:00
shankar0123 93e1dc598c fix: resolve frontend-to-backend mapping gaps across API types, config fields, and issuer IDs
Full audit of all ~100 backend API endpoints against frontend client functions
and TypeScript interfaces. Fixes field name mismatches, missing client functions,
phantom interface fields, type coercion for Go bool/int config fields, and
issuer type ID alignment with backend domain constants.

Backend:
- issuer.go/target.go: GUI-created entities default enabled=true (Go bool
  zero value was overriding DB DEFAULT)

Frontend types (types.ts):
- Certificate: fingerprint→fingerprint_sha256, phantom fields made optional
- CertificateVersion: fingerprint→fingerprint_sha256, chain_pem→pem_chain,
  removed phantom version/cert_pem fields
- Job: error_message→last_error (matches Go json tag)

Frontend client (client.ts):
- Added getNotification(id) and getAuditEvent(id) for existing backend routes

Frontend pages:
- CertificateDetailPage: derives serial/fingerprint/issuedAt from latest
  CertificateVersion instead of empty Certificate fields
- JobsPage/JobDetailPage: error_message→last_error
- TargetsPage: reload_cmd→reload_command, validate_cmd→validate_command,
  added missing config fields per backend structs (validate_command for
  NGINX/Apache, hostname/winrm_timeout for IIS, private_key/passphrase/
  cert_mode/key_mode for SSH, winrm_https/winrm_insecure for WinCertStore,
  create_keystore for JavaKeystore, mode for Dovecot), type coercion via
  buildConfigPayload() with BOOL_FIELDS/INT_FIELDS sets, IIS WinRM nesting
- TargetDetailPage: added passphrase to sensitiveKeys redaction
- issuerTypes.ts: type IDs aligned to backend constants (acme→ACME,
  local→GenericCA, stepca→StepCA, openssl→OpenSSL), backward compat aliases
  preserved, step-ca config fields updated to match backend struct

Utilities (utils.ts):
- formatDate/formatDateTime accept string|undefined|null

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 21:09:48 -04:00
shankar0123 e6088c79a3 feat(M35): dynamic target configuration with encrypted config, test connection, and GUI updates
Mirror M34's dynamic issuer config pattern for deployment targets: AES-256-GCM
encrypted config storage, sensitive field redaction in API responses, agent
heartbeat-based test connection endpoint, and full frontend updates including
test status indicators, source badges, and removal of stale hostname/status
fields from the Target interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 01:09:53 -04:00
shankar0123 03472072b8 test + docs: close 12 test gaps (~250 new tests) and expand testing guide to 34 parts
Implements all P0-P2 test gaps from docs/test-gap-prompt.md:
- Deployment service tests (20), target service tests (18), scheduler tests (8)
- Agent binary tests (48), CSR renewal tests (8), short-lived cert tests (7)
- Domain model tests (25), context cancellation tests (9), concurrency tests (7)
- Handler negative-path tests (23 across 5 files)
- Frontend error handling tests (86) and API client tests (7)

Expands testing-guide.md from 28 to 34 parts covering certificate export,
S/MIME/EKU, OCSP/DER CRL, body size limits, Apache/HAProxy connectors,
and sub-CA mode. Fixes stale profile count (4->5) and updates sign-off table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 17:57:25 -04:00
shankar0123 a00bb349c4 feat(m27): certificate export (PEM/PKCS#12) and S/MIME EKU support
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>
2026-03-28 16:16:19 -04:00
shankar0123 50c520e1ff feat: dashboard theme overhaul — light content area with branded teal sidebar
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>
2026-03-26 23:27:42 -04:00
shankar0123 ee75f149ae feat: M14 — Observability (dashboard charts, agent fleet, stats API, metrics, structured logging, rollback)
Backend: StatsService with 5 aggregation methods, JSON metrics endpoint, slog-based
structured logging middleware. Stats API: dashboard summary, certificates-by-status,
expiration timeline, job trends, issuance rate. 23 new backend tests.

Frontend: Recharts-powered dashboard with 4 charts (status pie, expiration heatmap,
job trends line, issuance bar), agent fleet overview page with OS/arch grouping and
version breakdown, deployment rollback buttons on version history. 7 new frontend tests.

78 API endpoints, 744+ total tests (658 Go + 86 Vitest).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 19:46:13 -04:00
shankar0123 8af4e42f44 feat: M13 — GUI operations (bulk ops, deployment timeline, policy editor, target wizard, audit export, short-lived creds)
Bulk certificate operations: multi-select checkboxes on certificates list with
bulk action bar for triggering renewal, revocation (with RFC 5280 reason modal
and progress bar), and owner reassignment across selected certificates.

Deployment status timeline: visual 4-step lifecycle pipeline (Requested → Issued
→ Deploying → Active) on certificate detail page, powered by per-cert job queries
with animated status indicators for active steps and failure states.

Inline policy editor: edit/save/cancel interface on certificate detail page for
changing renewal policy and certificate profile assignments via dropdown selectors
with lazy-loaded policy and profile lists.

Target connector configuration wizard: 3-step modal (Select Type → Configure →
Review) with type-specific configuration fields for NGINX, Apache, HAProxy, F5
BIG-IP, and IIS targets including required field validation.

Audit trail export: CSV and JSON download buttons on audit page with applied
filters preserved in export. Added action filter input for narrower searches.

Short-lived credentials dashboard: new page at /short-lived showing ephemeral
certificates (profile TTL < 1 hour) with live TTL countdown, auto-refresh every
10 seconds, profile lookup, and stats bar (active/expired/profiles).

DataTable enhanced with optional selectable/selectedKeys/onSelectionChange props
for checkbox multi-select with select-all toggle and row highlighting.

Frontend tests expanded from 53 to 79: full API client endpoint coverage for
profiles, owners, teams, agent groups, revocation, approval/rejection, policy
violations, and issuer creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 15:07:10 -04:00
shankar0123 762c523d59 feat: M15b — OCSP responder, DER CRL, short-lived exemption, revocation GUI
Backend:
- Embedded OCSP responder: GET /api/v1/ocsp/{issuer_id}/{serial} returns
  signed OCSP responses (good/revoked/unknown) using CA key
- DER-encoded X.509 CRL: GET /api/v1/crl/{issuer_id} returns proper DER CRL
  signed by issuing CA with 24h validity window
- Short-lived cert exemption: certs with profile TTL < 1 hour skip CRL/OCSP
  (expiry is sufficient revocation for ephemeral workloads)
- Extended issuer connector interface with GenerateCRL and SignOCSPResponse
- Local CA implements full CRL/OCSP signing; ACME and step-ca return
  appropriate "use native endpoint" errors
- IssuerConnectorAdapter bridges new methods between layers

Frontend:
- Revoke button on certificate detail page with RFC 5280 reason modal
- Revocation banner with reason display and timestamp
- Revocation status indicators in lifecycle section
- "Revoked" filter option in certificates list
- API client: revokeCertificate() function and Certificate type extensions

Tests: ~31 new tests across connector, service, handler, and adapter layers
Docs: milestones renumbered (M13-M14, M16-M18), M15b marked complete

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 14:39:10 -04:00
shankar0123 bb7a78352e fix: frontend error handling — ErrorBoundary, type-safe errors, stable keys
- React ErrorBoundary wrapping entire app for graceful crash recovery
- fetchJSON error handling uses try/catch instead of .catch() chain
- CertificateDetailPage: instanceof checks replace unsafe type casts
- DataTable: keyField prop replaces array index keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 01:20:32 -04:00
shankar0123 73c6bd1416 feat: add frontend action buttons, fix notification auth bug, add 53 Vitest tests
Bug fix:
- markNotificationRead was using raw fetch() without auth headers,
  bypassing the shared client's Authorization header. Moved to
  api/client.ts to use fetchJSON with proper auth.

New action buttons:
- CertificatesPage: "New Certificate" modal with form fields
- CertificateDetailPage: "Deploy" button with target selector modal,
  "Archive" button with confirmation
- IssuersPage: "Test Connection" and "Delete" per-row actions
- TargetsPage: "Delete" per-row action
- PoliciesPage: "Enable/Disable" toggle and "Delete" per-row actions

New API client functions:
- updateCertificate, archiveCertificate, registerAgent,
  createPolicy, updatePolicy, deletePolicy, getPolicyViolations,
  createIssuer, testIssuerConnection, deleteIssuer,
  createTarget, deleteTarget, markNotificationRead

Frontend tests (53 tests, 2 files):
- client.test.ts: 35 tests covering all API endpoints, auth headers,
  401 handling, error parsing, HTTP methods, request bodies
- utils.test.ts: 18 tests covering formatDate, formatDateTime,
  timeAgo, daysUntil, expiryColor

CI: Added "Run Frontend Tests" step to frontend-build job

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 00:05:21 -04:00
shankar0123 9e6756d02f Implement M5: hardening, input validation, and Vite+React+TS dashboard
Backend hardening:
- Fix 6 nginx.go non-constant format string build errors
- Add validation.go with hostname, PEM, and enum validators
- Apply input validation to all POST/PUT handlers (certificates,
  agents, CSR, policies, teams, owners, targets, issuers)
- Fix unchecked JSON decode in TriggerDeployment handler

Frontend (Vite + React + TypeScript):
- Migrate from single-file SPA to proper build pipeline
- 7 pages: Dashboard, Certificates (list+detail), Agents, Jobs,
  Notifications, Policies, Audit Trail
- TanStack Query for server state with auto-refetch intervals
- Certificate detail with version history and renewal trigger
- Job cancellation, status/type filtering, expiry countdowns
- Reusable components: DataTable, StatusBadge, ErrorState, PageHeader
- Dark theme with Tailwind CSS, sidebar nav via React Router

Server integration:
- Go server serves web/dist/ (Vite output) with SPA fallback
- Falls back to web/index.html for legacy mode
- .gitignore updated for web/node_modules/ and web/dist/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 01:19:19 -04:00