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
JavaScript
/**
* 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