UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

372 lines 13.6 kB
"use strict"; /** * @iota-big3/sdk-security - Distributed Tracing * OpenTelemetry-based request tracking across security services */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DistributedTracer = void 0; const events_1 = require("events"); class DistributedTracer extends events_1.EventEmitter { constructor(config) { super(); this.spans = new Map(); this.traces = new Map(); this.exportQueue = []; this.isActive = false; this.config = config; } async initialize() { if (!this.config.enabled || this.isActive) return; this.isActive = true; this.emit('tracer:initialized'); // Start export interval if (this.config.exportInterval) { this.exportInterval = setInterval(() => { this.exportSpans(); }, this.config.exportInterval); } } async shutdown() { if (!this.isActive) return; // Export remaining spans await this.exportSpans(); if (this.exportInterval) { clearInterval(this.exportInterval); } this.isActive = false; this.emit('tracer:shutdown'); } // Start a new trace or span startSpan(operationName, parentContext, attributes) { const span = { spanId: this.generateId(), traceId: parentContext?.traceId || this.generateId(), parentSpanId: parentContext?.spanId, operationName, startTime: Date.now(), status: 'OK', attributes: { 'service.name': this.config.serviceName, 'span.kind': parentContext ? 'internal' : 'server', ...attributes }, events: [], links: [] }; this.spans.set(span.spanId, span); // Add to trace const traceSpans = this.traces.get(span.traceId) || []; traceSpans.push(span); this.traces.set(span.traceId, traceSpans); // Sample decision if (Math.random() > this.config.samplingRate) { span.attributes['sampling.priority'] = 0; } this.emit('span:started', span); return span; } // End a span endSpan(spanId, status = 'OK') { const span = this.spans.get(spanId); if (!span || span.endTime) return; span.endTime = Date.now(); span.duration = span.endTime - span.startTime; span.status = status; // Add to export queue if sampled if (span.attributes['sampling.priority'] !== 0) { this.exportQueue.push(span); // Export immediately if queue is full if (this.exportQueue.length >= (this.config.maxQueueSize || 1000)) { this.exportSpans(); } } this.emit('span:ended', span); } // Add event to span addEvent(spanId, name, attributes) { const span = this.spans.get(spanId); if (!span) return; span.events.push({ name, timestamp: Date.now(), attributes }); } // Set span attributes setAttributes(spanId, attributes) { const span = this.spans.get(spanId); if (!span) return; Object.assign(span.attributes, attributes); } // Record exception recordException(spanId, error) { const span = this.spans.get(spanId); if (!span) return; span.status = 'ERROR'; span.attributes['error'] = true; this.addEvent(spanId, 'exception', { 'exception.type': error.name, 'exception.message': error.message, 'exception.stacktrace': error.stack }); } // Link spans addLink(spanId, linkedTraceId, linkedSpanId, attributes) { const span = this.spans.get(spanId); if (!span) return; span.links.push({ traceId: linkedTraceId, spanId: linkedSpanId, attributes }); } // Context propagation inject(span) { const headers = {}; // W3C Trace Context headers['traceparent'] = `00-${span.traceId}-${span.spanId}-01`; // B3 propagation (Zipkin) headers['X-B3-TraceId'] = span.traceId; headers['X-B3-SpanId'] = span.spanId; headers['X-B3-Sampled'] = span.attributes['sampling.priority'] === 0 ? '0' : '1'; if (span.parentSpanId) { headers['X-B3-ParentSpanId'] = span.parentSpanId; } return headers; } extract(headers) { // Try W3C Trace Context first if (headers['traceparent']) { const parts = headers['traceparent'].split('-'); if (parts.length >= 4) { return { traceId: parts[1], spanId: parts[2], traceFlags: parseInt(parts[3], 16), traceState: headers['tracestate'] }; } } // Try B3 propagation const b3TraceId = headers['x-b3-traceid']; const b3SpanId = headers['x-b3-spanid']; if (b3TraceId && b3SpanId) { return { traceId: b3TraceId, spanId: b3SpanId, traceFlags: headers['x-b3-sampled'] === '1' ? 1 : 0 }; } return null; } // Get current trace getCurrentTrace(traceId) { return this.traces.get(traceId) || []; } // Get span getSpan(spanId) { return this.spans.get(spanId); } // Security-specific tracing helpers traceAuthentication(userId, method, success) { const span = this.startSpan('authentication', undefined, { 'auth.user_id': userId, 'auth.method': method, 'auth.success': success, 'security.operation': 'authentication' }); if (!success) { span.status = 'ERROR'; this.addEvent(span.spanId, 'auth.failed', { reason: 'Invalid credentials' }); } return span; } traceAuthorization(userId, resource, action, allowed) { const span = this.startSpan('authorization', undefined, { 'authz.user_id': userId, 'authz.resource': resource, 'authz.action': action, 'authz.decision': allowed ? 'allow' : 'deny', 'security.operation': 'authorization' }); this.addEvent(span.spanId, 'authz.check', { 'decision.reason': allowed ? 'Permission granted' : 'Permission denied' }); return span; } traceComplianceCheck(framework, passed, score) { const span = this.startSpan('compliance_check', undefined, { 'compliance.framework': framework, 'compliance.passed': passed, 'compliance.score': score, 'security.operation': 'compliance' }); if (!passed) { span.status = 'ERROR'; this.addEvent(span.spanId, 'compliance.failed', { threshold: 80, actual: score }); } return span; } traceThreatDetection(threatType, severity, mitigated) { const span = this.startSpan('threat_detection', undefined, { 'threat.type': threatType, 'threat.severity': severity, 'threat.mitigated': mitigated, 'security.operation': 'threat_detection' }); if (severity === 'CRITICAL') { this.addEvent(span.spanId, 'threat.critical', { action: mitigated ? 'mitigated' : 'detected' }); } return span; } // Export spans async exportSpans() { if (this.exportQueue.length === 0) return; const spansToExport = [...this.exportQueue]; this.exportQueue = []; // In production, this would send to an OpenTelemetry collector // For now, we'll emit an event this.emit('spans:export', { spans: spansToExport, timestamp: Date.now() }); // Format for different backends const formattedSpans = { jaeger: this.formatForJaeger(spansToExport), zipkin: this.formatForZipkin(spansToExport), otlp: this.formatForOTLP(spansToExport) }; // Log export console.debug(`Exported ${spansToExport.length} spans`, { traceIds: [...new Set(spansToExport.map(s => s.traceId))], services: [...new Set(spansToExport.map(s => s.attributes['service.name']))] }); } // Format converters formatForJaeger(spans) { return { data: [{ traceID: spans[0].traceId, // Assuming all spans in the batch belong to the same trace spans: spans.map(span => ({ spanID: span.spanId, operationName: span.operationName, startTime: span.startTime * 1000, // microseconds duration: (span.duration || 0) * 1000, tags: Object.entries(span.attributes).map(([key, value]) => ({ key, value: String(value) })), logs: span.events.map(event => ({ timestamp: event.timestamp * 1000, fields: Object.entries(event.attributes || {}).map(([key, value]) => ({ key, value: String(value) })) })), references: [] // No parent/child links in mock })) }] }; } formatForZipkin(spans) { return spans.map(span => ({ traceId: span.traceId, id: span.spanId, name: span.operationName, timestamp: span.startTime * 1000, duration: (span.duration || 0) * 1000, tags: span.attributes, annotations: span.events.map(event => ({ timestamp: event.timestamp * 1000, value: event.name })), parentId: span.parentSpanId })); } formatForOTLP(spans) { return { resourceSpans: [{ resource: { attributes: [ { key: 'service.name', value: { stringValue: this.config.serviceName } } ] }, scopeSpans: [{ spans: spans.map(span => ({ traceId: this.hexToBase64(span.traceId), spanId: this.hexToBase64(span.spanId), name: span.operationName, startTimeUnixNano: span.startTime * 1000000, endTimeUnixNano: (span.endTime || span.startTime) * 1000000, attributes: Object.entries(span.attributes).map(([key, value]) => ({ key, value: this.toAttributeValue(value) })), events: span.events.map(event => ({ timeUnixNano: event.timestamp * 1000000, name: event.name, attributes: Object.entries(event.attributes || {}).map(([key, value]) => ({ key, value: this.toAttributeValue(value) })) })), status: { code: span.status === 'OK' ? 1 : 2 } })) }] }] }; } // Helper methods generateId() { return Date.now().toString(16) + Math.random().toString(16).substring(2); } hexToBase64(hex) { return Buffer.from(hex, 'hex').toString('base64'); } toAttributeValue(value) { if (typeof value === 'string') { return { stringValue: value }; } else if (typeof value === 'number') { return { intValue: value }; } else if (typeof value === 'boolean') { return { boolValue: value }; } else { return { stringValue: JSON.stringify(value) }; } } // Metrics getTracingMetrics() { const completedSpans = Array.from(this.spans.values()).filter(s => s.endTime); const errorSpans = completedSpans.filter(s => s.status === 'ERROR'); const totalDuration = completedSpans.reduce((sum, s) => sum + (s.duration || 0), 0); return { activeSpans: this.spans.size - completedSpans.length, completedSpans: completedSpans.length, errorRate: completedSpans.length > 0 ? (errorSpans.length / completedSpans.length) * 100 : 0, averageDuration: completedSpans.length > 0 ? totalDuration / completedSpans.length : 0, tracesInMemory: this.traces.size }; } } exports.DistributedTracer = DistributedTracer; //# sourceMappingURL=distributed-tracing.js.map