Files
sfera-new/src/graphql/security/external-monitoring-integration.ts
Veronika Smirnova d05f0a6a93 feat(security): обновление системы безопасности GraphQL и исправления ESLint
- Обновлены тесты безопасности для всех ролей (SELLER, WHOLESALE, FULFILLMENT, LOGIST)
- Улучшен мониторинг и аудит доступа к коммерческим данным
- Добавлена интеграция с внешними системами мониторинга
- Исправлены ESLint предупреждения в компонентах поставщика
- Обновлены middleware для безопасности GraphQL резолверов

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 18:49:32 +03:00

904 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Интеграция с внешними системами мониторинга
*
* Обеспечивает интеграцию системы безопасности SFERA с внешними SIEM/SOC системами,
* мониторингом инфраструктуры и системами уведомлений
*/
import { EventEmitter } from 'events'
import { PrismaClient } from '@prisma/client'
import { SecurityLogger } from '../../lib/security-logger'
import { SecurityAlert } from './types'
/**
* Конфигурация интеграций
*/
interface IntegrationConfig {
siem: {
enabled: boolean
type: 'SPLUNK' | 'ELASTIC_SIEM' | 'QRADAR' | 'SENTINEL'
endpoint: string
apiKey: string
format: 'CEF' | 'JSON' | 'SYSLOG'
}
prometheus: {
enabled: boolean
pushGateway: string
jobName: string
instance: string
}
grafana: {
enabled: boolean
apiUrl: string
apiKey: string
dashboardId: string
}
datadog: {
enabled: boolean
apiKey: string
appKey: string
site: string
}
newrelic: {
enabled: boolean
licenseKey: string
appId: string
}
slack: {
enabled: boolean
webhookUrl: string
channel: string
}
teams: {
enabled: boolean
webhookUrl: string
}
pagerduty: {
enabled: boolean
integrationKey: string
severity: 'critical' | 'error' | 'warning' | 'info'
}
webhook: {
enabled: boolean
url: string
headers: Record<string, string>
retries: number
}
}
/**
* Метрики для экспорта
*/
interface SecurityMetrics {
timestamp: Date
totalAlerts: number
alertsBySeverity: Record<string, number>
alertsByType: Record<string, number>
activeThreats: number
riskScore: number
systemHealth: {
monitoring: number
alerts: number
audit: number
filtering: number
}
userActivity: {
totalAccesses: number
uniqueUsers: number
suspiciousActivity: number
}
performance: {
avgResponseTime: number
processingRate: number
errorRate: number
}
}
/**
* События для внешних систем
*/
interface SecurityEvent {
id: string
timestamp: Date
eventType: 'ALERT_GENERATED' | 'THREAT_DETECTED' | 'SYSTEM_STATUS' | 'USER_ACTIVITY'
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
source: string
data: Record<string, unknown>
tags: string[]
}
export class ExternalMonitoringIntegration extends EventEmitter {
private static instance: ExternalMonitoringIntegration
private config: IntegrationConfig
private isActive = false
private metricsBuffer: SecurityMetrics[] = []
private eventsBuffer: SecurityEvent[] = []
constructor(private prisma: PrismaClient) {
super()
this.loadConfiguration()
}
/**
* Получить singleton instance
*/
static getInstance(prisma: PrismaClient): ExternalMonitoringIntegration {
if (!ExternalMonitoringIntegration.instance) {
ExternalMonitoringIntegration.instance = new ExternalMonitoringIntegration(prisma)
}
return ExternalMonitoringIntegration.instance
}
/**
* Запуск интеграций
*/
async start(): Promise<void> {
if (this.isActive) {
return
}
this.isActive = true
// Периодическая отправка метрик (каждые 30 секунд)
setInterval(() => {
this.pushMetrics()
}, 30000)
// Периодическая отправка событий (каждые 5 секунд)
setInterval(() => {
this.pushEvents()
}, 5000)
// Проверка состояния интеграций (каждые 5 минут)
setInterval(() => {
this.healthCheck()
}, 5 * 60 * 1000)
SecurityLogger.logSecurityInfo({
message: 'External monitoring integration started',
integrations: Object.keys(this.config).filter((key) => this.config[key as keyof IntegrationConfig]?.enabled),
})
this.emit('integration_started', {
timestamp: new Date(),
activeIntegrations: this.getActiveIntegrations(),
})
}
/**
* Остановка интеграций
*/
stop(): void {
this.isActive = false
this.removeAllListeners()
SecurityLogger.logSecurityInfo({ message: 'External monitoring integration stopped' })
}
/**
* Отправка алерта во внешние системы
*/
async sendSecurityAlert(alert: SecurityAlert): Promise<void> {
if (!this.isActive) {
return
}
const event: SecurityEvent = {
id: alert.id,
timestamp: alert.timestamp,
eventType: 'ALERT_GENERATED',
severity: alert.severity,
source: 'SFERA_SECURITY',
data: {
alertType: alert.type,
userId: alert.userId,
message: alert.message,
metadata: alert.metadata,
},
tags: ['security', 'alert', alert.type.toLowerCase(), alert.severity.toLowerCase()],
}
this.eventsBuffer.push(event)
// Для критичных алертов - немедленная отправка
if (alert.severity === 'CRITICAL' || alert.severity === 'HIGH') {
await this.pushCriticalEvent(event)
}
}
/**
* Отправка метрик безопасности
*/
async sendSecurityMetrics(metrics: SecurityMetrics): Promise<void> {
if (!this.isActive) {
return
}
this.metricsBuffer.push(metrics)
// Если буфер переполнен - принудительная отправка
if (this.metricsBuffer.length > 100) {
await this.pushMetrics()
}
}
/**
* Отправка события обнаружения угрозы
*/
async sendThreatDetection(threat: {
id: string
modelId: string
userId: string
threatType: string
confidence: number
riskScore: number
indicators: string[]
evidence: Record<string, unknown>
}): Promise<void> {
if (!this.isActive) {
return
}
const event: SecurityEvent = {
id: threat.id,
timestamp: new Date(),
eventType: 'THREAT_DETECTED',
severity: threat.riskScore > 80 ? 'CRITICAL' : threat.riskScore > 60 ? 'HIGH' : 'MEDIUM',
source: 'SFERA_THREAT_DETECTION',
data: {
modelId: threat.modelId,
userId: threat.userId,
threatType: threat.threatType,
confidence: threat.confidence,
riskScore: threat.riskScore,
indicators: threat.indicators,
evidence: threat.evidence,
},
tags: ['security', 'threat', 'detection', threat.threatType.toLowerCase()],
}
this.eventsBuffer.push(event)
// Для высокого risk score - немедленная отправка
if (threat.riskScore > 70) {
await this.pushCriticalEvent(event)
}
}
/**
* Обновление статуса системы
*/
async sendSystemStatus(status: {
monitoring: { status: string; uptime: number; metrics: Record<string, unknown> }
alerts: { status: string; uptime: number; metrics: Record<string, unknown> }
audit: { status: string; uptime: number; metrics: Record<string, unknown> }
dataFiltering: { status: string; uptime: number; metrics: Record<string, unknown> }
overallHealth: number
}): Promise<void> {
if (!this.isActive) {
return
}
const event: SecurityEvent = {
id: `system-status-${Date.now()}`,
timestamp: new Date(),
eventType: 'SYSTEM_STATUS',
severity: status.overallHealth > 95 ? 'LOW' : status.overallHealth > 80 ? 'MEDIUM' : 'HIGH',
source: 'SFERA_SYSTEM',
data: status,
tags: ['system', 'status', 'health'],
}
this.eventsBuffer.push(event)
}
/**
* Загрузка конфигурации
*/
private loadConfiguration(): void {
this.config = {
siem: {
enabled: process.env.SIEM_INTEGRATION_ENABLED === 'true',
type: (process.env.SIEM_TYPE as any) || 'ELASTIC_SIEM',
endpoint: process.env.SIEM_ENDPOINT || '',
apiKey: process.env.SIEM_API_KEY || '',
format: (process.env.SIEM_FORMAT as any) || 'JSON',
},
prometheus: {
enabled: process.env.PROMETHEUS_ENABLED === 'true',
pushGateway: process.env.PROMETHEUS_PUSH_GATEWAY || 'http://localhost:9091',
jobName: process.env.PROMETHEUS_JOB_NAME || 'sfera-security',
instance: process.env.PROMETHEUS_INSTANCE || 'sfera-api-01',
},
grafana: {
enabled: process.env.GRAFANA_ENABLED === 'true',
apiUrl: process.env.GRAFANA_API_URL || '',
apiKey: process.env.GRAFANA_API_KEY || '',
dashboardId: process.env.GRAFANA_DASHBOARD_ID || '',
},
datadog: {
enabled: process.env.DATADOG_ENABLED === 'true',
apiKey: process.env.DATADOG_API_KEY || '',
appKey: process.env.DATADOG_APP_KEY || '',
site: process.env.DATADOG_SITE || 'datadoghq.com',
},
newrelic: {
enabled: process.env.NEWRELIC_ENABLED === 'true',
licenseKey: process.env.NEWRELIC_LICENSE_KEY || '',
appId: process.env.NEWRELIC_APP_ID || '',
},
slack: {
enabled: process.env.SLACK_INTEGRATION_ENABLED === 'true',
webhookUrl: process.env.SLACK_WEBHOOK_URL || '',
channel: process.env.SLACK_CHANNEL || '#security-alerts',
},
teams: {
enabled: process.env.TEAMS_INTEGRATION_ENABLED === 'true',
webhookUrl: process.env.TEAMS_WEBHOOK_URL || '',
},
pagerduty: {
enabled: process.env.PAGERDUTY_ENABLED === 'true',
integrationKey: process.env.PAGERDUTY_INTEGRATION_KEY || '',
severity: (process.env.PAGERDUTY_SEVERITY as any) || 'error',
},
webhook: {
enabled: process.env.WEBHOOK_INTEGRATION_ENABLED === 'true',
url: process.env.WEBHOOK_URL || '',
headers: JSON.parse(process.env.WEBHOOK_HEADERS || '{}'),
retries: parseInt(process.env.WEBHOOK_RETRIES || '3'),
},
}
}
/**
* Отправка метрик
*/
private async pushMetrics(): Promise<void> {
if (this.metricsBuffer.length === 0) {
return
}
const metrics = [...this.metricsBuffer]
this.metricsBuffer.length = 0
const promises: Promise<void>[] = []
if (this.config.prometheus.enabled) {
promises.push(this.sendToPrometheus(metrics))
}
if (this.config.datadog.enabled) {
promises.push(this.sendToDatadog(metrics))
}
if (this.config.newrelic.enabled) {
promises.push(this.sendToNewRelic(metrics))
}
if (this.config.grafana.enabled) {
promises.push(this.sendToGrafana(metrics))
}
await Promise.allSettled(promises)
}
/**
* Отправка событий
*/
private async pushEvents(): Promise<void> {
if (this.eventsBuffer.length === 0) {
return
}
const events = [...this.eventsBuffer]
this.eventsBuffer.length = 0
const promises: Promise<void>[] = []
if (this.config.siem.enabled) {
promises.push(this.sendToSIEM(events))
}
if (this.config.webhook.enabled) {
promises.push(this.sendToWebhook(events))
}
await Promise.allSettled(promises)
}
/**
* Немедленная отправка критичного события
*/
private async pushCriticalEvent(event: SecurityEvent): Promise<void> {
const promises: Promise<void>[] = []
if (this.config.slack.enabled) {
promises.push(this.sendToSlack([event]))
}
if (this.config.teams.enabled) {
promises.push(this.sendToTeams([event]))
}
if (this.config.pagerduty.enabled && event.severity === 'CRITICAL') {
promises.push(this.sendToPagerDuty([event]))
}
await Promise.allSettled(promises)
}
/**
* Отправка в SIEM систему
*/
private async sendToSIEM(events: SecurityEvent[]): Promise<void> {
try {
const payload = events.map((event) => this.formatSIEMEvent(event))
const response = await fetch(this.config.siem.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.siem.apiKey}`,
},
body: JSON.stringify({ events: payload }),
})
if (!response.ok) {
throw new Error(`SIEM API error: ${response.status} ${response.statusText}`)
}
SecurityLogger.logSecurityInfo({
message: 'Events sent to SIEM',
count: events.length,
siemType: this.config.siem.type,
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'sendToSIEM',
eventsCount: events.length,
})
}
}
/**
* Отправка в Prometheus
*/
private async sendToPrometheus(metrics: SecurityMetrics[]): Promise<void> {
try {
const latestMetrics = metrics[metrics.length - 1]
const prometheusMetrics = this.formatPrometheusMetrics(latestMetrics)
const response = await fetch(`${this.config.prometheus.pushGateway}/metrics/job/${this.config.prometheus.jobName}/instance/${this.config.prometheus.instance}`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: prometheusMetrics,
})
if (!response.ok) {
throw new Error(`Prometheus API error: ${response.status} ${response.statusText}`)
}
SecurityLogger.logSecurityInfo({
message: 'Metrics sent to Prometheus',
metricsCount: Object.keys(latestMetrics).length,
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'sendToPrometheus',
metricsCount: metrics.length,
})
}
}
/**
* Отправка в Slack
*/
private async sendToSlack(events: SecurityEvent[]): Promise<void> {
try {
for (const event of events.filter(e => e.severity === 'CRITICAL' || e.severity === 'HIGH')) {
const slackPayload = {
channel: this.config.slack.channel,
username: 'SFERA Security Bot',
icon_emoji: ':warning:',
attachments: [
{
color: this.getSlackColor(event.severity),
title: `🚨 Security Alert: ${event.eventType}`,
text: `${event.data.message || 'Security event detected'}`,
fields: [
{ title: 'Severity', value: event.severity, short: true },
{ title: 'Source', value: event.source, short: true },
{ title: 'Event ID', value: event.id, short: true },
{ title: 'Timestamp', value: event.timestamp.toISOString(), short: true },
],
footer: 'SFERA Security System',
ts: Math.floor(event.timestamp.getTime() / 1000),
},
],
}
const response = await fetch(this.config.slack.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(slackPayload),
})
if (!response.ok) {
throw new Error(`Slack API error: ${response.status} ${response.statusText}`)
}
}
SecurityLogger.logSecurityInfo({
message: 'Critical events sent to Slack',
count: events.length,
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'sendToSlack',
eventsCount: events.length,
})
}
}
/**
* Отправка в Microsoft Teams
*/
private async sendToTeams(events: SecurityEvent[]): Promise<void> {
try {
for (const event of events.filter(e => e.severity === 'CRITICAL' || e.severity === 'HIGH')) {
const teamsPayload = {
'@type': 'MessageCard',
'@context': 'http://schema.org/extensions',
themeColor: this.getTeamsColor(event.severity),
summary: `SFERA Security Alert: ${event.eventType}`,
sections: [
{
activityTitle: `🚨 Security Alert: ${event.eventType}`,
activitySubtitle: event.data.message || 'Security event detected',
facts: [
{ name: 'Severity', value: event.severity },
{ name: 'Source', value: event.source },
{ name: 'Event ID', value: event.id },
{ name: 'Timestamp', value: event.timestamp.toISOString() },
],
markdown: true,
},
],
}
const response = await fetch(this.config.teams.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(teamsPayload),
})
if (!response.ok) {
throw new Error(`Teams API error: ${response.status} ${response.statusText}`)
}
}
SecurityLogger.logSecurityInfo({
message: 'Critical events sent to Teams',
count: events.length,
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'sendToTeams',
eventsCount: events.length,
})
}
}
/**
* Отправка в PagerDuty
*/
private async sendToPagerDuty(events: SecurityEvent[]): Promise<void> {
try {
for (const event of events.filter(e => e.severity === 'CRITICAL')) {
const pagerDutyPayload = {
routing_key: this.config.pagerduty.integrationKey,
event_action: 'trigger',
payload: {
summary: `SFERA Security Alert: ${event.eventType}`,
severity: this.config.pagerduty.severity,
source: event.source,
timestamp: event.timestamp.toISOString(),
custom_details: {
event_id: event.id,
event_type: event.eventType,
data: event.data,
tags: event.tags,
},
},
}
const response = await fetch('https://events.pagerduty.com/v2/enqueue', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(pagerDutyPayload),
})
if (!response.ok) {
throw new Error(`PagerDuty API error: ${response.status} ${response.statusText}`)
}
}
SecurityLogger.logSecurityInfo({
message: 'Critical events sent to PagerDuty',
count: events.length,
})
} catch (error) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'sendToPagerDuty',
eventsCount: events.length,
})
}
}
/**
* Отправка в webhook
*/
private async sendToWebhook(events: SecurityEvent[]): Promise<void> {
let attempts = 0
const maxAttempts = this.config.webhook.retries
while (attempts < maxAttempts) {
try {
const response = await fetch(this.config.webhook.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.config.webhook.headers,
},
body: JSON.stringify({
events,
timestamp: new Date().toISOString(),
source: 'SFERA_SECURITY',
}),
})
if (!response.ok) {
throw new Error(`Webhook API error: ${response.status} ${response.statusText}`)
}
SecurityLogger.logSecurityInfo({
message: 'Events sent to webhook',
count: events.length,
attempts: attempts + 1,
})
return
} catch (error) {
attempts++
if (attempts >= maxAttempts) {
SecurityLogger.logSecurityError(error as Error, {
operation: 'sendToWebhook',
eventsCount: events.length,
attempts,
})
return
}
// Экспоненциальная задержка
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempts) * 1000))
}
}
}
/**
* Заглушки для других интеграций
*/
private async sendToDatadog(metrics: SecurityMetrics[]): Promise<void> {
// TODO: Реализовать интеграцию с Datadog
console.log('📊 Datadog integration placeholder', metrics.length)
}
private async sendToNewRelic(metrics: SecurityMetrics[]): Promise<void> {
// TODO: Реализовать интеграцию с New Relic
console.log('📈 New Relic integration placeholder', metrics.length)
}
private async sendToGrafana(metrics: SecurityMetrics[]): Promise<void> {
// TODO: Реализовать интеграцию с Grafana
console.log('📋 Grafana integration placeholder', metrics.length)
}
/**
* Форматирование события для SIEM
*/
private formatSIEMEvent(event: SecurityEvent): Record<string, unknown> {
switch (this.config.siem.format) {
case 'CEF':
return this.formatCEF(event)
case 'SYSLOG':
return this.formatSyslog(event)
default:
return {
timestamp: event.timestamp.toISOString(),
id: event.id,
event_type: event.eventType,
severity: event.severity,
source: event.source,
data: event.data,
tags: event.tags,
}
}
}
/**
* Форматирование в CEF
*/
private formatCEF(event: SecurityEvent): Record<string, unknown> {
return {
cef_version: '0',
device_vendor: 'SFERA',
device_product: 'Security System',
device_version: '3.0.0',
signature_id: event.eventType,
name: event.id,
severity: this.mapCEFSeverity(event.severity),
extension: {
rt: event.timestamp.getTime(),
src: event.source,
msg: JSON.stringify(event.data),
},
}
}
/**
* Форматирование в Syslog
*/
private formatSyslog(event: SecurityEvent): Record<string, unknown> {
const priority = this.mapSyslogPriority(event.severity)
return {
priority,
timestamp: event.timestamp.toISOString(),
hostname: 'sfera-security',
tag: 'SFERA-SEC',
message: `[${event.eventType}] ${event.id}: ${JSON.stringify(event.data)}`,
}
}
/**
* Форматирование метрик для Prometheus
*/
private formatPrometheusMetrics(metrics: SecurityMetrics): string {
const lines: string[] = []
const timestamp = metrics.timestamp.getTime()
lines.push('# HELP sfera_security_alerts_total Total number of security alerts')
lines.push('# TYPE sfera_security_alerts_total counter')
lines.push(`sfera_security_alerts_total ${metrics.totalAlerts} ${timestamp}`)
lines.push('# HELP sfera_security_active_threats Current number of active threats')
lines.push('# TYPE sfera_security_active_threats gauge')
lines.push(`sfera_security_active_threats ${metrics.activeThreats} ${timestamp}`)
lines.push('# HELP sfera_security_risk_score Current overall risk score')
lines.push('# TYPE sfera_security_risk_score gauge')
lines.push(`sfera_security_risk_score ${metrics.riskScore} ${timestamp}`)
lines.push('# HELP sfera_security_user_accesses_total Total number of user data accesses')
lines.push('# TYPE sfera_security_user_accesses_total counter')
lines.push(`sfera_security_user_accesses_total ${metrics.userActivity.totalAccesses} ${timestamp}`)
return lines.join('\n')
}
/**
* Получение активных интеграций
*/
private getActiveIntegrations(): string[] {
const active: string[] = []
Object.entries(this.config).forEach(([name, config]) => {
if (config.enabled) {
active.push(name)
}
})
return active
}
/**
* Проверка здоровья интеграций
*/
private async healthCheck(): Promise<void> {
const healthStatus: Record<string, boolean> = {}
// Проверяем доступность каждой интеграции
if (this.config.siem.enabled) {
healthStatus.siem = await this.checkSIEMHealth()
}
if (this.config.prometheus.enabled) {
healthStatus.prometheus = await this.checkPrometheusHealth()
}
if (this.config.slack.enabled) {
healthStatus.slack = await this.checkSlackHealth()
}
SecurityLogger.logSecurityInfo({
message: 'Integration health check completed',
status: healthStatus,
})
this.emit('health_check', {
timestamp: new Date(),
status: healthStatus,
})
}
/**
* Заглушки для проверки здоровья
*/
private async checkSIEMHealth(): Promise<boolean> {
// TODO: Реализовать проверку SIEM
return true
}
private async checkPrometheusHealth(): Promise<boolean> {
// TODO: Реализовать проверку Prometheus
return true
}
private async checkSlackHealth(): Promise<boolean> {
// TODO: Реализовать проверку Slack
return true
}
/**
* Вспомогательные функции для форматирования
*/
private getSlackColor(severity: string): string {
const colors: Record<string, string> = {
CRITICAL: '#FF0000',
HIGH: '#FF8C00',
MEDIUM: '#FFD700',
LOW: '#00FF00',
}
return colors[severity] || '#808080'
}
private getTeamsColor(severity: string): string {
const colors: Record<string, string> = {
CRITICAL: 'FF0000',
HIGH: 'FF8C00',
MEDIUM: 'FFD700',
LOW: '00FF00',
}
return colors[severity] || '808080'
}
private mapCEFSeverity(severity: string): number {
const mapping: Record<string, number> = {
LOW: 3,
MEDIUM: 5,
HIGH: 7,
CRITICAL: 10,
}
return mapping[severity] || 5
}
private mapSyslogPriority(severity: string): number {
const mapping: Record<string, number> = {
LOW: 22, // local0.info
MEDIUM: 20, // local0.warning
HIGH: 19, // local0.error
CRITICAL: 18, // local0.critical
}
return mapping[severity] || 20
}
}