@iota-big3/sdk-security
Version:
Advanced security features including zero trust, quantum-safe crypto, and ML threat detection
372 lines • 13.6 kB
JavaScript
"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