mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-24 20:06:21 +02:00
130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
// Package main provides the SOC Ingest process (SEC-001 Process Isolation).
|
|
//
|
|
// Responsibility: HTTP endpoint, authentication, secret scanner,
|
|
// rate limiting, dedup, SQLite persistence.
|
|
// Forwards persisted events to soc-correlate via IPC.
|
|
//
|
|
// Usage:
|
|
//
|
|
// go run ./cmd/soc-ingest/
|
|
// SOC_DB_PATH=/data/soc.db SOC_INGEST_PORT=9750 go run ./cmd/soc-ingest/
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"github.com/syntrex-lab/gomcp/internal/application/soc"
|
|
"github.com/syntrex-lab/gomcp/internal/infrastructure/audit"
|
|
"github.com/syntrex-lab/gomcp/internal/infrastructure/ipc"
|
|
"github.com/syntrex-lab/gomcp/internal/infrastructure/logging"
|
|
"github.com/syntrex-lab/gomcp/internal/infrastructure/sqlite"
|
|
sochttp "github.com/syntrex-lab/gomcp/internal/transport/http"
|
|
)
|
|
|
|
func main() {
|
|
// SEC-003: Panic recovery.
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
buf := make([]byte, 4096)
|
|
n := runtime.Stack(buf, false)
|
|
fmt.Fprintf(os.Stderr, "SOC-INGEST FATAL PANIC: %v\n%s\n", r, buf[:n])
|
|
os.Exit(2)
|
|
}
|
|
}()
|
|
|
|
logger := logging.New(env("SOC_LOG_FORMAT", "text"), env("SOC_LOG_LEVEL", "info"))
|
|
slog.SetDefault(logger)
|
|
|
|
// SEC-003: Memory safety.
|
|
if limitStr := os.Getenv("GOMEMLIMIT"); limitStr == "" {
|
|
debug.SetMemoryLimit(256 * 1024 * 1024) // 256 MiB for ingest
|
|
}
|
|
|
|
port, _ := strconv.Atoi(env("SOC_INGEST_PORT", "9750"))
|
|
dbPath := env("SOC_DB_PATH", "soc.db")
|
|
|
|
logger.Info("starting SOC-INGEST (SEC-001 isolated process)",
|
|
"port", port, "db", dbPath,
|
|
"ipc_pipe", "soc-ingest-to-correlate",
|
|
)
|
|
|
|
// Infrastructure.
|
|
db, err := sqlite.Open(dbPath)
|
|
if err != nil {
|
|
logger.Error("database open failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer db.Close()
|
|
|
|
socRepo, err := sqlite.NewSOCRepo(db)
|
|
if err != nil {
|
|
logger.Error("SOC repo init failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
decisionLogger, err := audit.NewDecisionLogger(env("SOC_AUDIT_DIR", "."))
|
|
if err != nil {
|
|
logger.Error("decision logger init failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Service (ingest-only mode).
|
|
socSvc := soc.NewService(socRepo, decisionLogger)
|
|
|
|
// IPC: Connect to downstream soc-correlate.
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
correlateConn, err := ipc.DialWithRetry(ctx, "soc-ingest-to-correlate", 30)
|
|
if err != nil {
|
|
logger.Warn("soc-correlate not available — running in standalone ingest mode", "error", err)
|
|
} else {
|
|
ipcSender := ipc.NewBufferedSender(correlateConn, "soc-ingest-to-correlate")
|
|
defer ipcSender.Close()
|
|
|
|
// Subscribe to event bus and forward events via IPC.
|
|
eventCh := socSvc.EventBus().Subscribe("ipc-forwarder")
|
|
go func() {
|
|
for event := range eventCh {
|
|
msg, err := ipc.NewSOCMessage(ipc.SOCMsgEvent, event)
|
|
if err != nil {
|
|
logger.Error("ipc: marshal event", "error", err)
|
|
continue
|
|
}
|
|
if err := ipcSender.Send(msg); err != nil {
|
|
logger.Error("ipc: forward to correlate", "error", err)
|
|
}
|
|
}
|
|
}()
|
|
logger.Info("IPC connected to soc-correlate", "pending_buffer", ipc.BufferSize)
|
|
}
|
|
|
|
// HTTP server (ingest endpoints only).
|
|
srv := sochttp.New(socSvc, port)
|
|
|
|
// JWT auth.
|
|
if jwtSecret := env("SOC_JWT_SECRET", ""); jwtSecret != "" {
|
|
srv.SetJWTAuth([]byte(jwtSecret))
|
|
}
|
|
|
|
logger.Info("SOC-INGEST ready", "port", port)
|
|
if err := srv.Start(ctx); err != nil {
|
|
logger.Error("server failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func env(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|