mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-06 01:32:36 +02:00
Release prep: 54 engines, self-hosted signatures, i18n, dashboard updates
This commit is contained in:
parent
694e32be26
commit
41cbfd6e0a
178 changed files with 36008 additions and 399 deletions
373
internal/application/shadow_ai/soc_integration.go
Normal file
373
internal/application/shadow_ai/soc_integration.go
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
package shadow_ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ShadowAIController is the main orchestrator that ties together
|
||||
// detection, enforcement, SOC event emission, and statistics.
|
||||
type ShadowAIController struct {
|
||||
mu sync.RWMutex
|
||||
registry *PluginRegistry
|
||||
fallback *FallbackManager
|
||||
healthChecker *HealthChecker
|
||||
netDetector *NetworkDetector
|
||||
behavioral *BehavioralDetector
|
||||
docBridge *DocBridge
|
||||
approval *ApprovalEngine
|
||||
events []ShadowAIEvent // In-memory event store (bounded)
|
||||
maxEvents int
|
||||
socEventFn func(source, severity, category, description string, meta map[string]string) // Bridge to SOC event bus
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewShadowAIController creates the main Shadow AI Control orchestrator.
|
||||
func NewShadowAIController() *ShadowAIController {
|
||||
registry := NewPluginRegistry()
|
||||
RegisterDefaultPlugins(registry)
|
||||
return &ShadowAIController{
|
||||
registry: registry,
|
||||
fallback: NewFallbackManager(registry, "detect_only"),
|
||||
netDetector: NewNetworkDetector(),
|
||||
behavioral: NewBehavioralDetector(100),
|
||||
docBridge: NewDocBridge(),
|
||||
approval: NewApprovalEngine(),
|
||||
events: make([]ShadowAIEvent, 0, 1000),
|
||||
maxEvents: 10000,
|
||||
logger: slog.Default().With("component", "shadow-ai-controller"),
|
||||
}
|
||||
}
|
||||
|
||||
// SetSOCEventEmitter sets the function used to emit events into the SOC pipeline.
|
||||
func (c *ShadowAIController) SetSOCEventEmitter(fn func(source, severity, category, description string, meta map[string]string)) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.socEventFn = fn
|
||||
}
|
||||
|
||||
// Configure loads plugin configuration and initializes the integration layer.
|
||||
func (c *ShadowAIController) Configure(config *IntegrationConfig) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if err := c.registry.LoadPlugins(config); err != nil {
|
||||
return fmt.Errorf("failed to load plugins: %w", err)
|
||||
}
|
||||
|
||||
c.fallback = NewFallbackManager(c.registry, config.FallbackStrategy)
|
||||
c.fallback.SetEventLogger(func(event ShadowAIEvent) {
|
||||
c.recordEvent(event)
|
||||
})
|
||||
|
||||
interval := config.HealthCheckInterval
|
||||
if interval <= 0 {
|
||||
interval = 30 * time.Second
|
||||
}
|
||||
c.healthChecker = NewHealthChecker(c.registry, interval, func(vendor string, status PluginStatus, msg string) {
|
||||
c.emitSOCEvent("HIGH", "integration_health", msg, map[string]string{
|
||||
"vendor": vendor,
|
||||
"status": string(status),
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartHealthChecker starts continuous plugin health monitoring.
|
||||
func (c *ShadowAIController) StartHealthChecker(ctx context.Context) {
|
||||
if c.healthChecker != nil {
|
||||
go c.healthChecker.Start(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessNetworkEvent analyzes a network event and enforces policy.
|
||||
func (c *ShadowAIController) ProcessNetworkEvent(ctx context.Context, event NetworkEvent) *ShadowAIEvent {
|
||||
detected := c.netDetector.Analyze(event)
|
||||
if detected == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Record behavioral data.
|
||||
c.behavioral.RecordAccess(event.User, event.Destination, event.DataSize)
|
||||
|
||||
// Attempt to block.
|
||||
enforcedBy, err := c.fallback.BlockDomain(ctx, event.Destination, fmt.Sprintf("Shadow AI: %s", detected.AIService))
|
||||
if err != nil {
|
||||
c.logger.Error("enforcement failed", "destination", event.Destination, "error", err)
|
||||
}
|
||||
|
||||
if enforcedBy != "" {
|
||||
detected.Action = "blocked"
|
||||
detected.EnforcedBy = enforcedBy
|
||||
} else {
|
||||
detected.Action = "detected"
|
||||
}
|
||||
|
||||
detected.ID = genEventID()
|
||||
c.recordEvent(*detected)
|
||||
|
||||
// Emit to SOC event bus.
|
||||
c.emitSOCEvent("HIGH", "shadow_ai_usage",
|
||||
fmt.Sprintf("Shadow AI access detected: %s → %s", event.User, detected.AIService),
|
||||
map[string]string{
|
||||
"user": event.User,
|
||||
"hostname": event.Hostname,
|
||||
"destination": event.Destination,
|
||||
"ai_service": detected.AIService,
|
||||
"action": detected.Action,
|
||||
"enforced_by": detected.EnforcedBy,
|
||||
},
|
||||
)
|
||||
|
||||
return detected
|
||||
}
|
||||
|
||||
// ScanContent scans text content for AI API keys.
|
||||
func (c *ShadowAIController) ScanContent(content string) string {
|
||||
return c.netDetector.SignatureDB().ScanForAPIKeys(content)
|
||||
}
|
||||
|
||||
// ManualBlock manually blocks a domain or IP.
|
||||
func (c *ShadowAIController) ManualBlock(ctx context.Context, req BlockRequest) error {
|
||||
switch req.TargetType {
|
||||
case "domain":
|
||||
_, err := c.fallback.BlockDomain(ctx, req.Target, req.Reason)
|
||||
return err
|
||||
case "ip":
|
||||
_, err := c.fallback.BlockIP(ctx, req.Target, req.Duration, req.Reason)
|
||||
return err
|
||||
case "host":
|
||||
_, err := c.fallback.IsolateHost(ctx, req.Target)
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("unsupported target type: %s", req.TargetType)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStats returns aggregate shadow AI statistics.
|
||||
func (c *ShadowAIController) GetStats(timeRange string) ShadowAIStats {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
cutoff := parseCutoff(timeRange)
|
||||
stats := ShadowAIStats{
|
||||
TimeRange: timeRange,
|
||||
ByService: make(map[string]int),
|
||||
ByDepartment: make(map[string]int),
|
||||
}
|
||||
|
||||
violatorMap := make(map[string]int)
|
||||
|
||||
for _, e := range c.events {
|
||||
if e.Timestamp.Before(cutoff) {
|
||||
continue
|
||||
}
|
||||
stats.Total++
|
||||
switch e.Action {
|
||||
case "blocked":
|
||||
stats.Blocked++
|
||||
case "allowed", "approved":
|
||||
stats.Approved++
|
||||
case "pending":
|
||||
stats.Pending++
|
||||
}
|
||||
if e.AIService != "" {
|
||||
stats.ByService[e.AIService]++
|
||||
}
|
||||
if dept, ok := e.Metadata["department"]; ok {
|
||||
stats.ByDepartment[dept]++
|
||||
}
|
||||
if e.UserID != "" {
|
||||
violatorMap[e.UserID]++
|
||||
}
|
||||
}
|
||||
|
||||
// Build top violators list (sorted desc).
|
||||
for uid, count := range violatorMap {
|
||||
stats.TopViolators = append(stats.TopViolators, Violator{UserID: uid, Attempts: count})
|
||||
}
|
||||
// Sort by attempts descending, limit to 10.
|
||||
for i := 0; i < len(stats.TopViolators); i++ {
|
||||
for j := i + 1; j < len(stats.TopViolators); j++ {
|
||||
if stats.TopViolators[j].Attempts > stats.TopViolators[i].Attempts {
|
||||
stats.TopViolators[i], stats.TopViolators[j] = stats.TopViolators[j], stats.TopViolators[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(stats.TopViolators) > 10 {
|
||||
stats.TopViolators = stats.TopViolators[:10]
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// GetEvents returns recent shadow AI events (newest first).
|
||||
func (c *ShadowAIController) GetEvents(limit int) []ShadowAIEvent {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
total := len(c.events)
|
||||
if total == 0 {
|
||||
return nil
|
||||
}
|
||||
start := total - limit
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
// Return newest first.
|
||||
result := make([]ShadowAIEvent, 0, limit)
|
||||
for i := total - 1; i >= start; i-- {
|
||||
result = append(result, c.events[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetEvent returns a single event by ID.
|
||||
func (c *ShadowAIController) GetEvent(id string) (*ShadowAIEvent, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
for i := len(c.events) - 1; i >= 0; i-- {
|
||||
if c.events[i].ID == id {
|
||||
cp := c.events[i]
|
||||
return &cp, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// IntegrationHealth returns health status of all plugins.
|
||||
func (c *ShadowAIController) IntegrationHealth() []PluginHealth {
|
||||
return c.registry.AllHealth()
|
||||
}
|
||||
|
||||
// VendorHealth returns health for a specific vendor.
|
||||
func (c *ShadowAIController) VendorHealth(vendor string) (*PluginHealth, bool) {
|
||||
return c.registry.GetHealth(vendor)
|
||||
}
|
||||
|
||||
// Registry returns the plugin registry for direct access.
|
||||
func (c *ShadowAIController) Registry() *PluginRegistry {
|
||||
return c.registry
|
||||
}
|
||||
|
||||
// NetworkDetector returns the network detector for configuration.
|
||||
func (c *ShadowAIController) NetworkDetector() *NetworkDetector {
|
||||
return c.netDetector
|
||||
}
|
||||
|
||||
// BehavioralDetector returns the behavioral detector.
|
||||
func (c *ShadowAIController) BehavioralDetector() *BehavioralDetector {
|
||||
return c.behavioral
|
||||
}
|
||||
|
||||
// DocBridge returns the document review bridge.
|
||||
func (c *ShadowAIController) DocBridge() *DocBridge {
|
||||
return c.docBridge
|
||||
}
|
||||
|
||||
// ApprovalEngine returns the approval workflow engine.
|
||||
func (c *ShadowAIController) ApprovalEngine() *ApprovalEngine {
|
||||
return c.approval
|
||||
}
|
||||
|
||||
// ReviewDocument scans a document and creates an approval request if needed.
|
||||
func (c *ShadowAIController) ReviewDocument(docID, content, userID string) (*ScanResult, *ApprovalRequest) {
|
||||
result := c.docBridge.ScanDocument(docID, content, userID)
|
||||
|
||||
// Create approval request based on data classification.
|
||||
var req *ApprovalRequest
|
||||
if result.Status != DocReviewBlocked {
|
||||
req = c.approval.SubmitRequest(userID, docID, result.DataClass)
|
||||
}
|
||||
|
||||
// Emit SOC event for tracking.
|
||||
c.emitSOCEvent("MEDIUM", "shadow_ai_usage",
|
||||
fmt.Sprintf("Document review: %s by %s — %s (%s)",
|
||||
docID, userID, result.Status, result.DataClass),
|
||||
map[string]string{
|
||||
"user": userID,
|
||||
"doc_id": docID,
|
||||
"status": string(result.Status),
|
||||
"data_class": string(result.DataClass),
|
||||
"pii_count": fmt.Sprintf("%d", len(result.PIIFound)),
|
||||
},
|
||||
)
|
||||
|
||||
return result, req
|
||||
}
|
||||
|
||||
// GenerateComplianceReport generates a compliance report for the given period.
|
||||
func (c *ShadowAIController) GenerateComplianceReport(period string) ComplianceReport {
|
||||
stats := c.GetStats(period)
|
||||
docStats := c.docBridge.Stats()
|
||||
return ComplianceReport{
|
||||
GeneratedAt: time.Now(),
|
||||
Period: period,
|
||||
TotalInteractions: stats.Total,
|
||||
BlockedAttempts: stats.Blocked,
|
||||
ApprovedReviews: stats.Approved,
|
||||
PIIDetected: docStats["redacted"] + docStats["blocked"],
|
||||
SecretsDetected: docStats["blocked"],
|
||||
AuditComplete: true,
|
||||
Regulations: []string{"GDPR", "SOC2", "EU AI Act Article 15"},
|
||||
}
|
||||
}
|
||||
|
||||
// --- Internal helpers ---
|
||||
|
||||
func (c *ShadowAIController) recordEvent(event ShadowAIEvent) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.events = append(c.events, event)
|
||||
|
||||
// Evict oldest events if over capacity.
|
||||
if len(c.events) > c.maxEvents {
|
||||
excess := len(c.events) - c.maxEvents
|
||||
c.events = c.events[excess:]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ShadowAIController) emitSOCEvent(severity, category, description string, meta map[string]string) {
|
||||
c.mu.RLock()
|
||||
fn := c.socEventFn
|
||||
c.mu.RUnlock()
|
||||
|
||||
if fn != nil {
|
||||
fn("shadow-ai", severity, category, description, meta)
|
||||
}
|
||||
}
|
||||
|
||||
func parseCutoff(timeRange string) time.Time {
|
||||
switch timeRange {
|
||||
case "1h":
|
||||
return time.Now().Add(-1 * time.Hour)
|
||||
case "24h":
|
||||
return time.Now().Add(-24 * time.Hour)
|
||||
case "7d":
|
||||
return time.Now().Add(-7 * 24 * time.Hour)
|
||||
case "30d":
|
||||
return time.Now().Add(-30 * 24 * time.Hour)
|
||||
case "90d":
|
||||
return time.Now().Add(-90 * 24 * time.Hour)
|
||||
default:
|
||||
return time.Now().Add(-24 * time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
var eventCounter uint64
|
||||
var eventCounterMu sync.Mutex
|
||||
|
||||
func genEventID() string {
|
||||
eventCounterMu.Lock()
|
||||
eventCounter++
|
||||
id := eventCounter
|
||||
eventCounterMu.Unlock()
|
||||
return fmt.Sprintf("sai-%d-%d", time.Now().UnixMilli(), id)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue