mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-08 19:12:37 +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
|
|
@ -2,6 +2,7 @@ package soc
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -15,10 +16,28 @@ const (
|
|||
StatusFalsePositive IncidentStatus = "FALSE_POSITIVE"
|
||||
)
|
||||
|
||||
// IncidentNote represents an analyst investigation note.
|
||||
type IncidentNote struct {
|
||||
ID string `json:"id"`
|
||||
Author string `json:"author"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TimelineEntry represents a single event in the incident timeline.
|
||||
type TimelineEntry struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Type string `json:"type"` // event, playbook, status_change, note, assign
|
||||
Actor string `json:"actor"` // system, analyst name, playbook ID
|
||||
Description string `json:"description"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Incident represents a correlated security incident aggregated from multiple SOCEvents.
|
||||
// Each incident maintains a cryptographic anchor to the Decision Logger hash chain.
|
||||
type Incident struct {
|
||||
ID string `json:"id"` // INC-YYYY-NNNN
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
Status IncidentStatus `json:"status"`
|
||||
Severity EventSeverity `json:"severity"` // Max severity of constituent events
|
||||
Title string `json:"title"`
|
||||
|
|
@ -35,23 +54,37 @@ type Incident struct {
|
|||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
||||
AssignedTo string `json:"assigned_to,omitempty"`
|
||||
Notes []IncidentNote `json:"notes,omitempty"`
|
||||
Timeline []TimelineEntry `json:"timeline,omitempty"`
|
||||
}
|
||||
|
||||
// incidentCounter is a simple in-memory counter for generating incident IDs.
|
||||
var incidentCounter int
|
||||
// incidentCounter is an atomic counter for concurrent-safe incident ID generation.
|
||||
var incidentCounter atomic.Int64
|
||||
|
||||
// noteCounter for unique note IDs.
|
||||
var noteCounter atomic.Int64
|
||||
|
||||
// NewIncident creates a new incident from a correlation match.
|
||||
// Thread-safe: uses atomic increment for unique ID generation.
|
||||
func NewIncident(title string, severity EventSeverity, correlationRule string) Incident {
|
||||
incidentCounter++
|
||||
return Incident{
|
||||
ID: fmt.Sprintf("INC-%d-%04d", time.Now().Year(), incidentCounter),
|
||||
seq := incidentCounter.Add(1)
|
||||
now := time.Now()
|
||||
inc := Incident{
|
||||
ID: fmt.Sprintf("INC-%d-%04d", now.Year(), seq),
|
||||
Status: StatusOpen,
|
||||
Severity: severity,
|
||||
Title: title,
|
||||
CorrelationRule: correlationRule,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
inc.Timeline = append(inc.Timeline, TimelineEntry{
|
||||
Timestamp: now,
|
||||
Type: "created",
|
||||
Actor: "system",
|
||||
Description: fmt.Sprintf("Incident created by rule: %s", correlationRule),
|
||||
})
|
||||
return inc
|
||||
}
|
||||
|
||||
// AddEvent adds an event ID to the incident and updates severity if needed.
|
||||
|
|
@ -62,6 +95,12 @@ func (inc *Incident) AddEvent(eventID string, severity EventSeverity) {
|
|||
inc.Severity = severity
|
||||
}
|
||||
inc.UpdatedAt = time.Now()
|
||||
inc.Timeline = append(inc.Timeline, TimelineEntry{
|
||||
Timestamp: inc.UpdatedAt,
|
||||
Type: "event",
|
||||
Actor: "system",
|
||||
Description: fmt.Sprintf("Event %s correlated (severity: %s)", eventID, severity),
|
||||
})
|
||||
}
|
||||
|
||||
// SetAnchor sets the Decision Logger chain anchor for forensics (§5.6).
|
||||
|
|
@ -72,11 +111,72 @@ func (inc *Incident) SetAnchor(hash string, chainLength int) {
|
|||
}
|
||||
|
||||
// Resolve marks the incident as resolved.
|
||||
func (inc *Incident) Resolve(status IncidentStatus) {
|
||||
func (inc *Incident) Resolve(status IncidentStatus, actor string) {
|
||||
now := time.Now()
|
||||
oldStatus := inc.Status
|
||||
inc.Status = status
|
||||
inc.ResolvedAt = &now
|
||||
inc.UpdatedAt = now
|
||||
inc.Timeline = append(inc.Timeline, TimelineEntry{
|
||||
Timestamp: now,
|
||||
Type: "status_change",
|
||||
Actor: actor,
|
||||
Description: fmt.Sprintf("Status changed: %s → %s", oldStatus, status),
|
||||
})
|
||||
}
|
||||
|
||||
// Assign assigns an analyst to the incident.
|
||||
func (inc *Incident) Assign(analyst string) {
|
||||
prev := inc.AssignedTo
|
||||
inc.AssignedTo = analyst
|
||||
inc.UpdatedAt = time.Now()
|
||||
desc := fmt.Sprintf("Assigned to %s", analyst)
|
||||
if prev != "" {
|
||||
desc = fmt.Sprintf("Reassigned: %s → %s", prev, analyst)
|
||||
}
|
||||
inc.Timeline = append(inc.Timeline, TimelineEntry{
|
||||
Timestamp: inc.UpdatedAt,
|
||||
Type: "assign",
|
||||
Actor: analyst,
|
||||
Description: desc,
|
||||
})
|
||||
}
|
||||
|
||||
// ChangeStatus updates incident status without resolving.
|
||||
func (inc *Incident) ChangeStatus(status IncidentStatus, actor string) {
|
||||
old := inc.Status
|
||||
inc.Status = status
|
||||
inc.UpdatedAt = time.Now()
|
||||
if status == StatusResolved || status == StatusFalsePositive {
|
||||
now := time.Now()
|
||||
inc.ResolvedAt = &now
|
||||
}
|
||||
inc.Timeline = append(inc.Timeline, TimelineEntry{
|
||||
Timestamp: inc.UpdatedAt,
|
||||
Type: "status_change",
|
||||
Actor: actor,
|
||||
Description: fmt.Sprintf("Status: %s → %s", old, status),
|
||||
})
|
||||
}
|
||||
|
||||
// AddNote adds an investigation note from an analyst.
|
||||
func (inc *Incident) AddNote(author, content string) IncidentNote {
|
||||
seq := noteCounter.Add(1)
|
||||
note := IncidentNote{
|
||||
ID: fmt.Sprintf("note-%d", seq),
|
||||
Author: author,
|
||||
Content: content,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
inc.Notes = append(inc.Notes, note)
|
||||
inc.UpdatedAt = note.CreatedAt
|
||||
inc.Timeline = append(inc.Timeline, TimelineEntry{
|
||||
Timestamp: note.CreatedAt,
|
||||
Type: "note",
|
||||
Actor: author,
|
||||
Description: content,
|
||||
})
|
||||
return note
|
||||
}
|
||||
|
||||
// IsOpen returns true if the incident is not resolved.
|
||||
|
|
@ -98,3 +198,4 @@ func (inc *Incident) MTTR() time.Duration {
|
|||
}
|
||||
return inc.ResolvedAt.Sub(inc.CreatedAt)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue