From 308623af90e4e68a99cff51adda40e8303aa8a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Fri, 3 Jul 2026 12:03:08 -0600 Subject: [PATCH 1/4] fix[frontend](rules/custom_rules): changed legacy afterEvents by correlation --- .../alerting-rules/components/rule-form.tsx | 28 +++++++++---------- .../features/alerting-rules/lib/rule-yaml.ts | 6 ++-- .../pages/AlertingRulesPage.tsx | 6 ++-- .../services/alerting-rules-http.service.ts | 9 ++++-- frontend/src/shared/i18n/locales/de.json | 6 ++-- frontend/src/shared/i18n/locales/en.json | 6 ++-- frontend/src/shared/i18n/locales/es.json | 6 ++-- frontend/src/shared/i18n/locales/fr.json | 6 ++-- frontend/src/shared/i18n/locales/it.json | 6 ++-- frontend/src/shared/i18n/locales/pt.json | 6 ++-- frontend/src/shared/i18n/locales/ru.json | 6 ++-- 11 files changed, 47 insertions(+), 44 deletions(-) diff --git a/frontend/src/features/alerting-rules/components/rule-form.tsx b/frontend/src/features/alerting-rules/components/rule-form.tsx index 1de2d0c70..8f933bba0 100644 --- a/frontend/src/features/alerting-rules/components/rule-form.tsx +++ b/frontend/src/features/alerting-rules/components/rule-form.tsx @@ -35,7 +35,7 @@ export interface RuleFormState { ruleActive: boolean dataTypes: string[] definition: string - afterEvents: AfterStep[] + correlation: AfterStep[] groupBy: string[] deduplicateBy: string[] references: string[] @@ -59,9 +59,9 @@ function parseConditions(w: unknown): Condition[] { return { field: String(o.field ?? ''), operator: String(o.operator ?? 'filter_term'), value: valueToStr(o.value) } }) } -function parseSteps(after: unknown): AfterStep[] { - if (!Array.isArray(after)) return [] - return after.map((s) => { +function parseSteps(raw: unknown): AfterStep[] { + if (!Array.isArray(raw)) return [] + return raw.map((s) => { const o = (s ?? {}) as Record return { indexPattern: String(o.indexPattern ?? ''), @@ -86,7 +86,7 @@ export function ruleToForm(r?: CorrelationRule): RuleFormState { ruleActive: r?.ruleActive ?? true, dataTypes: (r?.dataTypes ?? []).filter((d) => d.included).map((d) => d.dataType), definition: r?.definition ?? '', - afterEvents: parseSteps(r?.afterEvents), + correlation: parseSteps(r?.correlation ?? r?.afterEvents), groupBy: strArray(r?.groupBy), deduplicateBy: strArray(r?.deduplicateBy), references: strArray(r?.references), @@ -122,7 +122,7 @@ export function formToInput(f: RuleFormState, relPath?: string): SaveRuleInput { description: f.description.trim(), references: f.references.filter(Boolean), definition: f.definition, - afterEvents: stepsToJson(f.afterEvents), + correlation: stepsToJson(f.correlation), groupBy: f.groupBy.filter(Boolean), deduplicateBy: f.deduplicateBy.filter(Boolean), ruleActive: f.ruleActive, @@ -134,7 +134,7 @@ export function formToInput(f: RuleFormState, relPath?: string): SaveRuleInput { export function RuleForm({ form, setForm, dataTypeOptions, t }: { form: RuleFormState; setForm: Dispatch>; dataTypeOptions: DataTypeOption[]; t: TFunction }) { const set = (k: K, v: RuleFormState[K]) => setForm((f) => ({ ...f, [k]: v })) - const setSteps = (steps: AfterStep[]) => set('afterEvents', steps) + const setSteps = (steps: AfterStep[]) => set('correlation', steps) // `where` condition: visual builder ⇄ raw CEL. Visual is the source when it // parses; otherwise we fall back to code for hand-written/advanced CEL. @@ -221,21 +221,21 @@ export function RuleForm({ form, setForm, dataTypeOptions, t }: { form: RuleForm )} -
-

{t('alertingRules.editor.afterEventsHint')}

+
+

{t('alertingRules.editor.correlationStepsHint')}

- {form.afterEvents.map((step, i) => ( + {form.correlation.map((step, i) => ( setSteps(form.afterEvents.map((x, idx) => (idx === i ? s : x)))} - onRemove={() => setSteps(form.afterEvents.filter((_, idx) => idx !== i))} + onChange={(s) => setSteps(form.correlation.map((x, idx) => (idx === i ? s : x)))} + onRemove={() => setSteps(form.correlation.filter((_, idx) => idx !== i))} /> ))}
{steps.length > 0 && ( -
+
)} @@ -607,7 +607,7 @@ function CelNodeView({ node, t, depth }: { node: CelNode; t: TFunction; depth: n } /** Read-only cards for the correlation (after-events) steps. */ -function AfterEventsView({ steps, t }: { steps: ReturnType['afterEvents']; t: TFunction }) { +function AfterEventsView({ steps, t }: { steps: ReturnType['correlation']; t: TFunction }) { return (
{steps.map((s, i) => ( diff --git a/frontend/src/features/alerting-rules/services/alerting-rules-http.service.ts b/frontend/src/features/alerting-rules/services/alerting-rules-http.service.ts index 4ff6257f9..c5a8588af 100644 --- a/frontend/src/features/alerting-rules/services/alerting-rules-http.service.ts +++ b/frontend/src/features/alerting-rules/services/alerting-rules-http.service.ts @@ -46,8 +46,10 @@ export interface ImportRulesResponse { /** * A correlation (alerting) rule. Identity is `relPath` (YAML-direct). `definition` - * is the CEL `where` condition (a JSON-encoded string); `afterEvents`/`references`/ + * is the CEL `where` condition (a JSON-encoded string); `correlation`/`references`/ * `groupBy`/`deduplicateBy` are arbitrary JSON the backend stores verbatim. + * `afterEvents` is the legacy name for `correlation` — still populated by older + * backends/rule files, read as a fallback. */ export interface CorrelationRule { relPath: string @@ -61,7 +63,8 @@ export interface CorrelationRule { description: string references: unknown definition: string - afterEvents: unknown + correlation?: unknown + afterEvents?: unknown // legacy: pre-rename field name groupBy: unknown deduplicateBy: unknown ruleLastUpdate?: string @@ -82,7 +85,7 @@ export interface SaveRuleInput { description: string references: unknown definition: string - afterEvents: unknown + correlation: unknown groupBy: unknown deduplicateBy: unknown ruleActive: boolean diff --git a/frontend/src/shared/i18n/locales/de.json b/frontend/src/shared/i18n/locales/de.json index ab072ba04..95c1cfc0c 100644 --- a/frontend/src/shared/i18n/locales/de.json +++ b/frontend/src/shared/i18n/locales/de.json @@ -4213,7 +4213,7 @@ "updated": "Aktualisiert", "relPath": "Pfad", "definition": "Bedingung (CEL)", - "afterEvents": "Korrelationsschritte", + "correlationSteps": "Korrelationsschritte", "correlation": "Korrelation", "groupBy": "Gruppieren nach", "deduplicateBy": "Deduplizieren nach", @@ -4236,8 +4236,8 @@ "definitionSection": "Definition", "where": "Bedingung (CEL)", "whereHint": "CEL-Ausdruck je Ereignis ausgewertet", - "afterEvents": "Korrelationsschritte (afterEvents)", - "afterEventsHint": "JSON-Array", + "correlationSteps": "Korrelationsschritte", + "correlationStepsHint": "JSON-Array", "jsonArray": "JSON-Array", "nameRequired": "Name ist erforderlich", "definitionRequired": "Die Bedingung ist erforderlich", diff --git a/frontend/src/shared/i18n/locales/en.json b/frontend/src/shared/i18n/locales/en.json index 907ba36bf..4106196e5 100644 --- a/frontend/src/shared/i18n/locales/en.json +++ b/frontend/src/shared/i18n/locales/en.json @@ -4465,7 +4465,7 @@ "updated": "Updated", "relPath": "Path", "definition": "Condition (CEL)", - "afterEvents": "Correlation steps", + "correlationSteps": "Correlation steps", "correlation": "Correlation", "groupBy": "Group by", "deduplicateBy": "Deduplicate by", @@ -4488,8 +4488,8 @@ "definitionSection": "Definition", "where": "Condition (CEL)", "whereHint": "CEL expression evaluated per event", - "afterEvents": "Correlation steps (afterEvents)", - "afterEventsHint": "JSON array", + "correlationSteps": "Correlation steps", + "correlationStepsHint": "JSON array", "jsonArray": "JSON array", "nameRequired": "Name is required", "definitionRequired": "The condition is required", diff --git a/frontend/src/shared/i18n/locales/es.json b/frontend/src/shared/i18n/locales/es.json index 54345a314..84e0aef53 100644 --- a/frontend/src/shared/i18n/locales/es.json +++ b/frontend/src/shared/i18n/locales/es.json @@ -4389,7 +4389,7 @@ "updated": "Actualizada", "relPath": "Ruta", "definition": "Condición (CEL)", - "afterEvents": "Pasos de correlación", + "correlationSteps": "Pasos de correlación", "correlation": "Correlación", "groupBy": "Agrupar por", "deduplicateBy": "Deduplicar por", @@ -4412,8 +4412,8 @@ "definitionSection": "Definición", "where": "Condición (CEL)", "whereHint": "Expresión CEL evaluada por evento", - "afterEvents": "Pasos de correlación (afterEvents)", - "afterEventsHint": "array JSON", + "correlationSteps": "Pasos de correlación", + "correlationStepsHint": "array JSON", "jsonArray": "array JSON", "nameRequired": "El nombre es obligatorio", "definitionRequired": "La condición es obligatoria", diff --git a/frontend/src/shared/i18n/locales/fr.json b/frontend/src/shared/i18n/locales/fr.json index 78ed500ac..9db62b374 100644 --- a/frontend/src/shared/i18n/locales/fr.json +++ b/frontend/src/shared/i18n/locales/fr.json @@ -4213,7 +4213,7 @@ "updated": "Mise à jour", "relPath": "Chemin", "definition": "Condition (CEL)", - "afterEvents": "Étapes de corrélation", + "correlationSteps": "Étapes de corrélation", "correlation": "Corrélation", "groupBy": "Grouper par", "deduplicateBy": "Dédupliquer par", @@ -4236,8 +4236,8 @@ "definitionSection": "Définition", "where": "Condition (CEL)", "whereHint": "Expression CEL évaluée par événement", - "afterEvents": "Étapes de corrélation (afterEvents)", - "afterEventsHint": "tableau JSON", + "correlationSteps": "Étapes de corrélation", + "correlationStepsHint": "tableau JSON", "jsonArray": "tableau JSON", "nameRequired": "Le nom est requis", "definitionRequired": "La condition est requise", diff --git a/frontend/src/shared/i18n/locales/it.json b/frontend/src/shared/i18n/locales/it.json index e154f2591..66b78436a 100644 --- a/frontend/src/shared/i18n/locales/it.json +++ b/frontend/src/shared/i18n/locales/it.json @@ -4213,7 +4213,7 @@ "updated": "Aggiornata", "relPath": "Percorso", "definition": "Condizione (CEL)", - "afterEvents": "Passi di correlazione", + "correlationSteps": "Passi di correlazione", "correlation": "Correlazione", "groupBy": "Raggruppa per", "deduplicateBy": "Deduplica per", @@ -4236,8 +4236,8 @@ "definitionSection": "Definizione", "where": "Condizione (CEL)", "whereHint": "Espressione CEL valutata per evento", - "afterEvents": "Passi di correlazione (afterEvents)", - "afterEventsHint": "array JSON", + "correlationSteps": "Passi di correlazione", + "correlationStepsHint": "array JSON", "jsonArray": "array JSON", "nameRequired": "Il nome è obbligatorio", "definitionRequired": "La condizione è obbligatoria", diff --git a/frontend/src/shared/i18n/locales/pt.json b/frontend/src/shared/i18n/locales/pt.json index 9d74d6531..e5037cd1c 100644 --- a/frontend/src/shared/i18n/locales/pt.json +++ b/frontend/src/shared/i18n/locales/pt.json @@ -4389,7 +4389,7 @@ "updated": "Atualizada", "relPath": "Caminho", "definition": "Condição (CEL)", - "afterEvents": "Passos de correlação", + "correlationSteps": "Passos de correlação", "correlation": "Correlação", "groupBy": "Agrupar por", "deduplicateBy": "Deduplicar por", @@ -4412,8 +4412,8 @@ "definitionSection": "Definição", "where": "Condição (CEL)", "whereHint": "Expressão CEL avaliada por evento", - "afterEvents": "Passos de correlação (afterEvents)", - "afterEventsHint": "array JSON", + "correlationSteps": "Passos de correlação", + "correlationStepsHint": "array JSON", "jsonArray": "array JSON", "nameRequired": "O nome é obrigatório", "definitionRequired": "A condição é obrigatória", diff --git a/frontend/src/shared/i18n/locales/ru.json b/frontend/src/shared/i18n/locales/ru.json index a7788d675..3f062dc26 100644 --- a/frontend/src/shared/i18n/locales/ru.json +++ b/frontend/src/shared/i18n/locales/ru.json @@ -4023,7 +4023,7 @@ "updated": "Обновлено", "relPath": "Путь", "definition": "Условие (CEL)", - "afterEvents": "Шаги корреляции", + "correlationSteps": "Шаги корреляции", "correlation": "Корреляция", "groupBy": "Группировать по", "deduplicateBy": "Дедупликация по", @@ -4046,8 +4046,8 @@ "definitionSection": "Определение", "where": "Условие (CEL)", "whereHint": "Выражение CEL, вычисляемое для каждого события", - "afterEvents": "Шаги корреляции (afterEvents)", - "afterEventsHint": "JSON-массив", + "correlationSteps": "Шаги корреляции", + "correlationStepsHint": "JSON-массив", "jsonArray": "JSON-массив", "nameRequired": "Название обязательно", "definitionRequired": "Условие обязательно", From ab793823057302f03e729875d9c404cb2a08e16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Fri, 3 Jul 2026 12:06:22 -0600 Subject: [PATCH 2/4] fix[backend](rules): changed after events by correlation (keeping retro compatibility --- .../domain/correlation_rule.go | 20 +++++++++++- .../eventprocessing/dto/correlation_rule.go | 31 +++++++++++++++++-- backend/modules/eventprocessing/module.go | 2 +- .../usecase/correlation_rule.go | 30 +++++++++++++----- .../eventprocessing/usecase/rule_bootstrap.go | 12 ++++++- .../eventprocessing/usecase/rule_model.go | 2 +- .../eventprocessing/usecase/rule_store.go | 4 +-- 7 files changed, 84 insertions(+), 17 deletions(-) diff --git a/backend/modules/eventprocessing/domain/correlation_rule.go b/backend/modules/eventprocessing/domain/correlation_rule.go index 60d0fec76..fe34d47f7 100644 --- a/backend/modules/eventprocessing/domain/correlation_rule.go +++ b/backend/modules/eventprocessing/domain/correlation_rule.go @@ -1,6 +1,9 @@ package domain -import "time" +import ( + "fmt" + "time" +) type UtmCorrelationRules struct { ID int64 `gorm:"column:id;primaryKey"` @@ -28,7 +31,10 @@ type UtmCorrelationRules struct { SystemOwner bool `gorm:"column:system_owner;not null"` // Nullable JSON TEXT columns for advanced rule configuration. + //[deprecated] only keeped for compatibility AfterEventsDef string `gorm:"column:rule_after_events_def"` + // + CorrelationDef string `gorm:"column:rule_correlation_def"` RuleGroupByDef string `gorm:"column:rule_group_by_def"` DeduplicateByDef string `gorm:"column:rule_deduplicate_by_def"` @@ -38,4 +44,16 @@ type UtmCorrelationRules struct { DataTypes []UtmDataTypes `gorm:"many2many:utm_group_rules_data_type;joinForeignKey:rule_id;joinReferences:data_type_id"` } +func (self *UtmCorrelationRules) GetCorrelationDef() (string,error){ + var correlate string + if self.AfterEventsDef!="" && self.CorrelationDef !=""{ + return "",fmt.Errorf("only one afterEvents or correlation allowed") + }else if self.AfterEventsDef!="" { + correlate = self.AfterEventsDef + }else{ + correlate= self.CorrelationDef + } + return correlate,nil +} + func (UtmCorrelationRules) TableName() string { return "utm_correlation_rules" } diff --git a/backend/modules/eventprocessing/dto/correlation_rule.go b/backend/modules/eventprocessing/dto/correlation_rule.go index 22d6349ca..1b34208ab 100644 --- a/backend/modules/eventprocessing/dto/correlation_rule.go +++ b/backend/modules/eventprocessing/dto/correlation_rule.go @@ -2,6 +2,7 @@ package dto import ( "encoding/json" + "fmt" "time" ) @@ -23,7 +24,29 @@ type RuleDataTypeResponse struct { SystemOwner bool `json:"systemOwner"` } + +type CorrelationOwner struct{ + //[deprecated] only keeped for compatibility + AfterEventsDef json.RawMessage `json:"afterEvents"` + // + CorrelationDef json.RawMessage `json:"correlation"` +} + +func (self *CorrelationOwner) GetCorrelationDef() (json.RawMessage,error){ + var correlate json.RawMessage + if self.AfterEventsDef!=nil && self.CorrelationDef !=nil{ + return nil,fmt.Errorf("only one afterEvents or correlation allowed") + }else if self.AfterEventsDef!=nil { + correlate = self.AfterEventsDef + }else{ + correlate= self.CorrelationDef + } + return correlate,nil +} + + type CreateCorrelationRuleRequest struct { + CorrelationOwner // JSON tags match Java UtmCorrelationRulesDTO field names for wire compatibility. RuleName string `json:"name"` RuleAdversary string `json:"adversary"` @@ -38,7 +61,6 @@ type CreateCorrelationRuleRequest struct { RuleReferencesDef json.RawMessage `json:"references"` RuleDefinitionDef json.RawMessage `json:"definition"` - AfterEventsDef json.RawMessage `json:"afterEvents"` RuleGroupByDef json.RawMessage `json:"groupBy"` DeduplicateByDef json.RawMessage `json:"deduplicateBy"` @@ -47,7 +69,10 @@ type CreateCorrelationRuleRequest struct { DataTypes []DataTypeRef `json:"dataTypes"` } + + type UpdateCorrelationRuleRequest struct { + CorrelationOwner // RelPath identifies the rule to update (the YAML-direct identity). RelPath string `json:"relPath"` @@ -64,7 +89,6 @@ type UpdateCorrelationRuleRequest struct { RuleReferencesDef json.RawMessage `json:"references"` RuleDefinitionDef json.RawMessage `json:"definition"` - AfterEventsDef json.RawMessage `json:"afterEvents"` RuleGroupByDef json.RawMessage `json:"groupBy"` DeduplicateByDef json.RawMessage `json:"deduplicateBy"` @@ -73,6 +97,7 @@ type UpdateCorrelationRuleRequest struct { DataTypes []DataTypeRef `json:"dataTypes"` } + type CorrelationRuleResponse struct { // RelPath is the rule identity (replaces the legacy numeric id). RelPath string `json:"relPath"` @@ -90,7 +115,7 @@ type CorrelationRuleResponse struct { RuleReferencesDef json.RawMessage `json:"references"` RuleDefinitionDef json.RawMessage `json:"definition"` - AfterEventsDef json.RawMessage `json:"afterEvents"` + CorrelationDef json.RawMessage `json:"correlation"` RuleGroupByDef json.RawMessage `json:"groupBy"` DeduplicateByDef json.RawMessage `json:"deduplicateBy"` diff --git a/backend/modules/eventprocessing/module.go b/backend/modules/eventprocessing/module.go index 538349033..0089afed8 100644 --- a/backend/modules/eventprocessing/module.go +++ b/backend/modules/eventprocessing/module.go @@ -126,5 +126,5 @@ func (m *Module) GetIngestionStatsUsecase() connectors.IngestionStatsUsecase { } func (m *Module) AfterEventsByRuleName(name string) (json.RawMessage, bool) { - return m.ruleStore.AfterEventsByName(name) + return m.ruleStore.CorrelationsByName(name) } diff --git a/backend/modules/eventprocessing/usecase/correlation_rule.go b/backend/modules/eventprocessing/usecase/correlation_rule.go index bfbbcf792..70b30e6ca 100644 --- a/backend/modules/eventprocessing/usecase/correlation_rule.go +++ b/backend/modules/eventprocessing/usecase/correlation_rule.go @@ -24,16 +24,24 @@ func NewCorrelationRuleUsecase(store *RuleStore) connectors.CorrelationRuleUseca return &correlationRuleUsecase{store: store} } + + func (u *correlationRuleUsecase) Create(_ context.Context, req dto.CreateCorrelationRuleRequest) error { if len(req.DataTypes) == 0 { return domain.ErrDataTypesRequired } - if err := validateRuleContent(req.RuleDefinitionDef, req.AfterEventsDef); err != nil { + + correlate,err:= req.GetCorrelationDef() + if err!=nil{ + return err + } + + if err := validateRuleContent(req.RuleDefinitionDef,correlate ); err != nil { return err } rule := buildRule(req.RuleName, req.RuleAdversary, req.RuleConfidentiality, req.RuleIntegrity, req.RuleAvailability, req.RuleCategory, req.RuleTechnique, req.RuleDescription, - req.RuleReferencesDef, req.RuleDefinitionDef, req.AfterEventsDef, req.RuleGroupByDef, + req.RuleReferencesDef, req.RuleDefinitionDef, correlate, req.RuleGroupByDef, req.DeduplicateByDef, req.DataTypes) created, err := u.store.Create(rule) @@ -105,10 +113,10 @@ func validateImportedRule(r Rule) error { if strings.TrimSpace(r.Where) == "" { return domain.ErrCorrelationRuleNullDefinition } - if r.AfterEvents == nil { + if r.Correlation == nil { return nil } - raw, err := json.Marshal(r.AfterEvents) + raw, err := json.Marshal(r.Correlation) if err != nil { return fmt.Errorf("%w: afterEvents is not serializable", domain.ErrCorrelationRuleInvalidContent) } @@ -131,12 +139,18 @@ func (u *correlationRuleUsecase) Update(_ context.Context, req dto.UpdateCorrela if len(req.DataTypes) == 0 { return domain.ErrDataTypesRequired } - if err := validateRuleContent(req.RuleDefinitionDef, req.AfterEventsDef); err != nil { + + correlate,err:= req.GetCorrelationDef() + if err!=nil{ + return err + } + + if err := validateRuleContent(req.RuleDefinitionDef, correlate); err != nil { return err } rule := buildRule(req.RuleName, req.RuleAdversary, req.RuleConfidentiality, req.RuleIntegrity, req.RuleAvailability, req.RuleCategory, req.RuleTechnique, req.RuleDescription, - req.RuleReferencesDef, req.RuleDefinitionDef, req.AfterEventsDef, req.RuleGroupByDef, + req.RuleReferencesDef, req.RuleDefinitionDef, correlate, req.RuleGroupByDef, req.DeduplicateByDef, req.DataTypes) if _, err := u.store.Update(req.RelPath, rule); err != nil { @@ -210,7 +224,7 @@ func buildRule(name, adversary string, conf, integ, avail int, category, techniq Impact: Impact{Confidentiality: conf, Integrity: integ, Availability: avail}, Where: rawToWhere(def), References: rawToAnySlice(refs), - AfterEvents: rawToAny(after), + Correlation: rawToAny(after), GroupBy: rawToStrSlice(groupBy), DeduplicateBy: rawToStrSlice(dedup), } @@ -237,7 +251,7 @@ func storedToResponse(sr *StoredRule) *dto.CorrelationRuleResponse { RuleDescription: sr.Description, RuleReferencesDef: anyToRaw(sr.References), RuleDefinitionDef: anyToRaw(sr.Where), - AfterEventsDef: anyToRaw(sr.AfterEvents), + CorrelationDef: anyToRaw(sr.Correlation), RuleGroupByDef: anyToRaw(sr.GroupBy), DeduplicateByDef: anyToRaw(sr.DeduplicateBy), RuleLastUpdate: &mod, diff --git a/backend/modules/eventprocessing/usecase/rule_bootstrap.go b/backend/modules/eventprocessing/usecase/rule_bootstrap.go index 258480d9d..db368128e 100644 --- a/backend/modules/eventprocessing/usecase/rule_bootstrap.go +++ b/backend/modules/eventprocessing/usecase/rule_bootstrap.go @@ -264,6 +264,16 @@ func legacyToRule(row *domain.UtmCorrelationRules) Rule { for _, dt := range row.DataTypes { names = append(names, dt.DataType) } + + correlation,err:=row.GetCorrelationDef() + if err!=nil{ + catcher.Error("skipping invalid after events of rule, %s",err,map[string]any{ + "rule":row.RuleName, + "afterEvents":row.AfterEventsDef, + "correlation":row.CorrelationDef, + }) + } + return Rule{ Name: row.RuleName, Adversary: row.RuleAdversary, @@ -274,7 +284,7 @@ func legacyToRule(row *domain.UtmCorrelationRules) Rule { Impact: Impact{Confidentiality: row.RuleConfidentiality, Integrity: row.RuleIntegrity, Availability: row.RuleAvailability}, Where: rawToWhere(toRaw(row.RuleDefinitionDef)), References: rawToAnySlice(toRaw(row.RuleReferencesDef)), - AfterEvents: rawToAny(toRaw(row.AfterEventsDef)), + Correlation: rawToAny(toRaw(correlation)), GroupBy: rawToStrSlice(toRaw(row.RuleGroupByDef)), DeduplicateBy: rawToStrSlice(toRaw(row.DeduplicateByDef)), } diff --git a/backend/modules/eventprocessing/usecase/rule_model.go b/backend/modules/eventprocessing/usecase/rule_model.go index e2a88dd12..b48bcd2d1 100644 --- a/backend/modules/eventprocessing/usecase/rule_model.go +++ b/backend/modules/eventprocessing/usecase/rule_model.go @@ -38,7 +38,7 @@ type Rule struct { References []any `yaml:"references,omitempty"` Description string `yaml:"description,omitempty"` Where string `yaml:"where"` - AfterEvents any `yaml:"afterEvents,omitempty"` + Correlation any `yaml:"correlation,omitempty"` GroupBy []string `yaml:"groupBy,omitempty"` DeduplicateBy []string `yaml:"deduplicateBy,omitempty"` } diff --git a/backend/modules/eventprocessing/usecase/rule_store.go b/backend/modules/eventprocessing/usecase/rule_store.go index 3ff949ca9..fa24be62e 100644 --- a/backend/modules/eventprocessing/usecase/rule_store.go +++ b/backend/modules/eventprocessing/usecase/rule_store.go @@ -162,12 +162,12 @@ func (s *RuleStore) FindByName(name string) *StoredRule { return nil } -func (s *RuleStore) AfterEventsByName(name string) (json.RawMessage, bool) { +func (s *RuleStore) CorrelationsByName(name string) (json.RawMessage, bool) { sr := s.FindByName(name) if sr == nil { return nil, false } - return anyToRaw(sr.AfterEvents), true + return anyToRaw(sr.Correlation), true } // List applies the filter, sorts by name and paginates. It returns the page From bb5130c02103d80fbe03c7e59edd29504ae6158a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Fri, 3 Jul 2026 12:37:59 -0600 Subject: [PATCH 3/4] fix[backend](rules): removed modifications on legacy rule schema as it does not have correlation param --- .../eventprocessing/domain/correlation_rule.go | 16 ++-------------- .../eventprocessing/dto/correlation_rule.go | 2 +- .../eventprocessing/usecase/rule_bootstrap.go | 10 +--------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/backend/modules/eventprocessing/domain/correlation_rule.go b/backend/modules/eventprocessing/domain/correlation_rule.go index fe34d47f7..3632743ea 100644 --- a/backend/modules/eventprocessing/domain/correlation_rule.go +++ b/backend/modules/eventprocessing/domain/correlation_rule.go @@ -1,7 +1,6 @@ package domain import ( - "fmt" "time" ) @@ -31,10 +30,10 @@ type UtmCorrelationRules struct { SystemOwner bool `gorm:"column:system_owner;not null"` // Nullable JSON TEXT columns for advanced rule configuration. - //[deprecated] only keeped for compatibility + //[deprecated] only kept for compatibility AfterEventsDef string `gorm:"column:rule_after_events_def"` // - CorrelationDef string `gorm:"column:rule_correlation_def"` + RuleGroupByDef string `gorm:"column:rule_group_by_def"` DeduplicateByDef string `gorm:"column:rule_deduplicate_by_def"` @@ -44,16 +43,5 @@ type UtmCorrelationRules struct { DataTypes []UtmDataTypes `gorm:"many2many:utm_group_rules_data_type;joinForeignKey:rule_id;joinReferences:data_type_id"` } -func (self *UtmCorrelationRules) GetCorrelationDef() (string,error){ - var correlate string - if self.AfterEventsDef!="" && self.CorrelationDef !=""{ - return "",fmt.Errorf("only one afterEvents or correlation allowed") - }else if self.AfterEventsDef!="" { - correlate = self.AfterEventsDef - }else{ - correlate= self.CorrelationDef - } - return correlate,nil -} func (UtmCorrelationRules) TableName() string { return "utm_correlation_rules" } diff --git a/backend/modules/eventprocessing/dto/correlation_rule.go b/backend/modules/eventprocessing/dto/correlation_rule.go index 1b34208ab..954424b8e 100644 --- a/backend/modules/eventprocessing/dto/correlation_rule.go +++ b/backend/modules/eventprocessing/dto/correlation_rule.go @@ -26,7 +26,7 @@ type RuleDataTypeResponse struct { type CorrelationOwner struct{ - //[deprecated] only keeped for compatibility + //[deprecated] only kept for compatibility AfterEventsDef json.RawMessage `json:"afterEvents"` // CorrelationDef json.RawMessage `json:"correlation"` diff --git a/backend/modules/eventprocessing/usecase/rule_bootstrap.go b/backend/modules/eventprocessing/usecase/rule_bootstrap.go index db368128e..0d80a2fd5 100644 --- a/backend/modules/eventprocessing/usecase/rule_bootstrap.go +++ b/backend/modules/eventprocessing/usecase/rule_bootstrap.go @@ -265,14 +265,6 @@ func legacyToRule(row *domain.UtmCorrelationRules) Rule { names = append(names, dt.DataType) } - correlation,err:=row.GetCorrelationDef() - if err!=nil{ - catcher.Error("skipping invalid after events of rule, %s",err,map[string]any{ - "rule":row.RuleName, - "afterEvents":row.AfterEventsDef, - "correlation":row.CorrelationDef, - }) - } return Rule{ Name: row.RuleName, @@ -284,7 +276,7 @@ func legacyToRule(row *domain.UtmCorrelationRules) Rule { Impact: Impact{Confidentiality: row.RuleConfidentiality, Integrity: row.RuleIntegrity, Availability: row.RuleAvailability}, Where: rawToWhere(toRaw(row.RuleDefinitionDef)), References: rawToAnySlice(toRaw(row.RuleReferencesDef)), - Correlation: rawToAny(toRaw(correlation)), + Correlation: rawToAny(toRaw(row.AfterEventsDef)), GroupBy: rawToStrSlice(toRaw(row.RuleGroupByDef)), DeduplicateBy: rawToStrSlice(toRaw(row.DeduplicateByDef)), } From c958671abacf137c1f67ff123523e17621c3869a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20S=C3=A1nchez?= Date: Fri, 3 Jul 2026 12:44:37 -0600 Subject: [PATCH 4/4] fix[backend](rules): removed after events completelly from current alerts rule flow --- .../eventprocessing/dto/correlation_rule.go | 25 ++----------------- .../usecase/correlation_rule.go | 10 ++------ 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/backend/modules/eventprocessing/dto/correlation_rule.go b/backend/modules/eventprocessing/dto/correlation_rule.go index 954424b8e..0d58af5a2 100644 --- a/backend/modules/eventprocessing/dto/correlation_rule.go +++ b/backend/modules/eventprocessing/dto/correlation_rule.go @@ -2,7 +2,6 @@ package dto import ( "encoding/json" - "fmt" "time" ) @@ -25,28 +24,7 @@ type RuleDataTypeResponse struct { } -type CorrelationOwner struct{ - //[deprecated] only kept for compatibility - AfterEventsDef json.RawMessage `json:"afterEvents"` - // - CorrelationDef json.RawMessage `json:"correlation"` -} - -func (self *CorrelationOwner) GetCorrelationDef() (json.RawMessage,error){ - var correlate json.RawMessage - if self.AfterEventsDef!=nil && self.CorrelationDef !=nil{ - return nil,fmt.Errorf("only one afterEvents or correlation allowed") - }else if self.AfterEventsDef!=nil { - correlate = self.AfterEventsDef - }else{ - correlate= self.CorrelationDef - } - return correlate,nil -} - - type CreateCorrelationRuleRequest struct { - CorrelationOwner // JSON tags match Java UtmCorrelationRulesDTO field names for wire compatibility. RuleName string `json:"name"` RuleAdversary string `json:"adversary"` @@ -65,6 +43,7 @@ type CreateCorrelationRuleRequest struct { DeduplicateByDef json.RawMessage `json:"deduplicateBy"` RuleActive bool `json:"ruleActive"` + CorrelationDef json.RawMessage `json:"correlation"` DataTypes []DataTypeRef `json:"dataTypes"` } @@ -72,7 +51,6 @@ type CreateCorrelationRuleRequest struct { type UpdateCorrelationRuleRequest struct { - CorrelationOwner // RelPath identifies the rule to update (the YAML-direct identity). RelPath string `json:"relPath"` @@ -93,6 +71,7 @@ type UpdateCorrelationRuleRequest struct { DeduplicateByDef json.RawMessage `json:"deduplicateBy"` RuleActive bool `json:"ruleActive"` + CorrelationDef json.RawMessage `json:"correlation"` DataTypes []DataTypeRef `json:"dataTypes"` } diff --git a/backend/modules/eventprocessing/usecase/correlation_rule.go b/backend/modules/eventprocessing/usecase/correlation_rule.go index 70b30e6ca..24f4ae46c 100644 --- a/backend/modules/eventprocessing/usecase/correlation_rule.go +++ b/backend/modules/eventprocessing/usecase/correlation_rule.go @@ -31,10 +31,7 @@ func (u *correlationRuleUsecase) Create(_ context.Context, req dto.CreateCorrela return domain.ErrDataTypesRequired } - correlate,err:= req.GetCorrelationDef() - if err!=nil{ - return err - } + correlate:= req.CorrelationDef if err := validateRuleContent(req.RuleDefinitionDef,correlate ); err != nil { return err @@ -140,10 +137,7 @@ func (u *correlationRuleUsecase) Update(_ context.Context, req dto.UpdateCorrela return domain.ErrDataTypesRequired } - correlate,err:= req.GetCorrelationDef() - if err!=nil{ - return err - } + correlate:= req.CorrelationDef if err := validateRuleContent(req.RuleDefinitionDef, correlate); err != nil { return err