UNPKG

okta-mcp-server

Version:

Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching

391 lines 13.4 kB
/** * Audit tool handlers */ import { z } from 'zod'; import { logger } from '../../utils/logger.js'; // Input schemas for validation const queryAuditLogsSchema = z.object({ startTime: z.string().datetime().optional(), endTime: z.string().datetime().optional(), actors: z.array(z.string()).optional(), actions: z.array(z.string()).optional(), resources: z.array(z.string()).optional(), status: z.array(z.enum(['success', 'failure', 'error'])).optional(), limit: z.number().min(1).max(10000).default(100), offset: z.number().min(0).default(0), orderBy: z.enum(['timestamp', 'duration']).default('timestamp'), orderDirection: z.enum(['asc', 'desc']).default('desc'), }); const getAuditStatisticsSchema = z.object({ startTime: z.string().datetime(), endTime: z.string().datetime(), }); const exportAuditLogsSchema = z.object({ format: z.enum(['json', 'csv', 'siem']), startTime: z.string().datetime(), endTime: z.string().datetime(), filters: z .object({ actors: z.array(z.string()).optional(), actions: z.array(z.string()).optional(), resources: z.array(z.string()).optional(), status: z.array(z.string()).optional(), }) .optional(), includeHeaders: z.boolean().default(true), }); const checkAuditIntegritySchema = z.object({ hoursBack: z.number().min(1).max(720).default(24), }); const generateComplianceReportSchema = z.object({ standard: z.enum(['SOC2', 'HIPAA', 'GDPR', 'PCI-DSS', 'ISO27001']), startTime: z.string().datetime(), endTime: z.string().datetime(), }); const getAuditMetricsSchema = z.object({ timeWindow: z.enum(['1h', '6h', '24h', '7d', '30d']).default('24h'), includeTopN: z.number().min(1).max(100).default(10), }); /** * Get audit logger from container */ function getAuditLogger(container) { if (container) { const logger = container.get('auditLogger'); if (logger) return logger; } // Fallback to global instance const { getGlobalAuditLogger } = require('../../infrastructure/audit/index.js'); return getGlobalAuditLogger(); } /** * Query audit logs */ export async function handleQueryAuditLogs(args, container) { try { const input = queryAuditLogsSchema.parse(args); const auditLogger = getAuditLogger(container); const entries = await auditLogger.query({ startTime: input.startTime ? new Date(input.startTime) : undefined, endTime: input.endTime ? new Date(input.endTime) : undefined, actors: input.actors, actions: input.actions, resources: input.resources, status: input.status, limit: input.limit, offset: input.offset, orderBy: input.orderBy, orderDirection: input.orderDirection, }); return { content: [ { type: 'text', text: JSON.stringify({ entries, count: entries.length, hasMore: entries.length === input.limit, }, null, 2), }, ], }; } catch (error) { logger.error('Failed to query audit logs:', error); return { content: [ { type: 'text', text: `Error querying audit logs: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Get audit statistics */ export async function handleGetAuditStatistics(args, container) { try { const input = getAuditStatisticsSchema.parse(args); const auditLogger = getAuditLogger(container); const stats = await auditLogger.getStatistics(new Date(input.startTime), new Date(input.endTime)); return { content: [ { type: 'text', text: JSON.stringify(stats, null, 2), }, ], }; } catch (error) { logger.error('Failed to get audit statistics:', error); return { content: [ { type: 'text', text: `Error getting audit statistics: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Export audit logs */ export async function handleExportAuditLogs(args, container) { try { const input = exportAuditLogsSchema.parse(args); const auditLogger = getAuditLogger(container); const filters = input.filters ? { status: input.filters.status, actors: input.filters.actors, actions: input.filters.actions, resources: input.filters.resources, } : undefined; const exportData = await auditLogger.export({ format: input.format, startTime: new Date(input.startTime), endTime: new Date(input.endTime), filters, includeHeaders: input.includeHeaders, }); return { content: [ { type: 'text', text: exportData, }, ], }; } catch (error) { logger.error('Failed to export audit logs:', error); return { content: [ { type: 'text', text: `Error exporting audit logs: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Check audit integrity */ export async function handleCheckAuditIntegrity(args, container) { try { const input = checkAuditIntegritySchema.parse(args); const auditLogger = getAuditLogger(container); const result = await auditLogger.checkIntegrity(input.hoursBack); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { logger.error('Failed to check audit integrity:', error); return { content: [ { type: 'text', text: `Error checking audit integrity: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Generate compliance report */ export async function handleGenerateComplianceReport(args, container) { try { const input = generateComplianceReportSchema.parse(args); const auditLogger = getAuditLogger(container); // Get audit data for the period const entries = await auditLogger.query({ startTime: new Date(input.startTime), endTime: new Date(input.endTime), }); // Generate compliance report based on standard const report = await generateComplianceReport(input.standard, entries, new Date(input.startTime), new Date(input.endTime)); return { content: [ { type: 'text', text: JSON.stringify(report, null, 2), }, ], }; } catch (error) { logger.error('Failed to generate compliance report:', error); return { content: [ { type: 'text', text: `Error generating compliance report: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Get audit metrics */ export async function handleGetAuditMetrics(args, container) { try { const input = getAuditMetricsSchema.parse(args); const auditLogger = getAuditLogger(container); // Calculate time window const now = new Date(); const timeWindows = { '1h': 60 * 60 * 1000, '6h': 6 * 60 * 60 * 1000, '24h': 24 * 60 * 60 * 1000, '7d': 7 * 24 * 60 * 60 * 1000, '30d': 30 * 24 * 60 * 60 * 1000, }; const startTime = new Date(now.getTime() - timeWindows[input.timeWindow]); // Get statistics const stats = await auditLogger.getStatistics(startTime, now); // Get recent entries for real-time metrics const recentEntries = await auditLogger.query({ startTime, endTime: now, limit: 1000, }); // Calculate additional metrics const metrics = { ...stats, timeWindow: input.timeWindow, requestRate: recentEntries.length / (timeWindows[input.timeWindow] / 1000 / 60), // per minute errorRate: (stats.errorCount / stats.totalEntries) * 100, successRate: (stats.successCount / stats.totalEntries) * 100, p95Duration: calculatePercentile(recentEntries.map((e) => e.performance?.duration || 0).filter((d) => d > 0), 0.95), p99Duration: calculatePercentile(recentEntries.map((e) => e.performance?.duration || 0).filter((d) => d > 0), 0.99), }; return { content: [ { type: 'text', text: JSON.stringify(metrics, null, 2), }, ], }; } catch (error) { logger.error('Failed to get audit metrics:', error); return { content: [ { type: 'text', text: `Error getting audit metrics: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } } /** * Generate compliance report based on standard */ async function generateComplianceReport(standard, entries, startTime, endTime) { const findings = []; switch (standard) { case 'SOC2': // SOC2 specific checks findings.push({ requirement: 'CC6.1 - Logical and Physical Access Controls', status: (entries.every((e) => e.actor.id !== 'anonymous') ? 'compliant' : 'non-compliant'), evidence: ['All audit entries have identified actors'], }); findings.push({ requirement: 'CC7.2 - System Monitoring', status: 'compliant', evidence: ['Audit logging system is active and capturing all operations'], }); findings.push({ requirement: 'CC8.1 - Change Management', status: (entries.filter((e) => e.action.type.includes('update')).length > 0 ? 'compliant' : 'partial'), evidence: ['System tracks all modification operations'], }); break; case 'GDPR': // GDPR specific checks findings.push({ requirement: 'Article 30 - Records of Processing Activities', status: 'compliant', evidence: ['Comprehensive audit trail of all data processing activities'], }); findings.push({ requirement: 'Article 32 - Security of Processing', status: (entries.every((e) => e.hash) ? 'compliant' : 'non-compliant'), evidence: ['Audit logs include integrity verification'], }); break; case 'HIPAA': // HIPAA specific checks findings.push({ requirement: '164.312(b) - Audit Controls', status: 'compliant', evidence: [ 'Hardware, software, and procedural mechanisms that record and examine activity', ], }); findings.push({ requirement: '164.308(a)(1)(ii)(D) - Information System Activity Review', status: 'compliant', evidence: ['Regular review of audit logs and access reports'], }); break; default: findings.push({ requirement: 'General Audit Logging', status: 'compliant', evidence: ['Basic audit logging requirements met'], }); } const summary = { totalRequirements: findings.length, compliant: findings.filter((f) => f.status === 'compliant').length, nonCompliant: findings.filter((f) => f.status === 'non-compliant').length, partial: findings.filter((f) => f.status === 'partial').length, }; return { standard, generatedAt: new Date(), period: { start: startTime, end: endTime }, findings, summary, }; } /** * Calculate percentile from array of numbers */ function calculatePercentile(values, percentile) { if (values.length === 0) return 0; const sorted = values.sort((a, b) => a - b); const index = Math.ceil(sorted.length * percentile) - 1; return sorted[index] || 0; } //# sourceMappingURL=handlers.js.map