feat(V2.2): bulk revocation — filter-based fleet-wide certificate revocation

Add POST /api/v1/certificates/bulk-revoke with filter criteria (profile_id,
owner_id, agent_id, issuer_id, team_id, certificate_ids), partial-failure
tolerance, and audit trail. Includes MCP tool, CLI command (certs bulk-revoke),
server-side bulk modal in GUI replacing client-side sequential loop, OpenAPI
spec, compliance mapping updates, and 21 new tests (12 service, 7 handler,
1 CLI, 1 frontend).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shankar
2026-04-16 00:06:34 -04:00
parent cdb448dfe5
commit 4e3927e8b4
25 changed files with 1264 additions and 39 deletions
+10
View File
@@ -11,6 +11,7 @@ import {
updateCertificate,
archiveCertificate,
revokeCertificate,
bulkRevokeCertificates,
exportCertificatePEM,
downloadCertificatePEM,
exportCertificatePKCS12,
@@ -288,6 +289,15 @@ describe('API Client', () => {
expect(init.method).toBe('POST');
expect(JSON.parse(init.body)).toEqual({ reason: 'keyCompromise' });
});
it('bulkRevokeCertificates sends POST with criteria', async () => {
mockFetch.mockReturnValueOnce(mockJsonResponse({ total_matched: 3, total_revoked: 2, total_skipped: 1, total_failed: 0 }));
await bulkRevokeCertificates({ reason: 'keyCompromise', profile_id: 'prof-tls', certificate_ids: ['mc-1', 'mc-2'] });
const [url, init] = mockFetch.mock.calls[0];
expect(url).toBe('/api/v1/certificates/bulk-revoke');
expect(init.method).toBe('POST');
expect(JSON.parse(init.body)).toEqual({ reason: 'keyCompromise', profile_id: 'prof-tls', certificate_ids: ['mc-1', 'mc-2'] });
});
});
// ─── Agents ─────────────────────────────────────────
+24
View File
@@ -95,6 +95,30 @@ export const revokeCertificate = (id: string, reason: string) =>
body: JSON.stringify({ reason }),
});
export interface BulkRevokeCriteria {
reason: string;
profile_id?: string;
owner_id?: string;
agent_id?: string;
issuer_id?: string;
team_id?: string;
certificate_ids?: string[];
}
export interface BulkRevokeResult {
total_matched: number;
total_revoked: number;
total_skipped: number;
total_failed: number;
errors?: { certificate_id: string; error: string }[];
}
export const bulkRevokeCertificates = (criteria: BulkRevokeCriteria) =>
fetchJSON<BulkRevokeResult>(`${BASE}/certificates/bulk-revoke`, {
method: 'POST',
body: JSON.stringify(criteria),
});
// Certificate Export
export const exportCertificatePEM = (id: string) =>
fetchJSON<{ cert_pem: string; chain_pem: string; full_pem: string }>(`${BASE}/certificates/${id}/export/pem`);