Skip to content

Commit 2094202

Browse files
Backlog/v12 rule after events (#2325)
* fix[frontend](rules/custom_rules): changed legacy afterEvents by correlation * fix[backend](rules): changed after events by correlation (keeping retro compatibility * fix[backend](rules): removed modifications on legacy rule schema as it does not have correlation param * fix[backend](rules): removed after events completelly from current alerts rule flow
1 parent 436bc11 commit 2094202

18 files changed

Lines changed: 84 additions & 61 deletions

File tree

backend/modules/eventprocessing/domain/correlation_rule.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package domain
22

3-
import "time"
3+
import (
4+
"time"
5+
)
46

57
type UtmCorrelationRules struct {
68
ID int64 `gorm:"column:id;primaryKey"`
@@ -28,7 +30,10 @@ type UtmCorrelationRules struct {
2830
SystemOwner bool `gorm:"column:system_owner;not null"`
2931

3032
// Nullable JSON TEXT columns for advanced rule configuration.
33+
//[deprecated] only kept for compatibility
3134
AfterEventsDef string `gorm:"column:rule_after_events_def"`
35+
//
36+
3237
RuleGroupByDef string `gorm:"column:rule_group_by_def"`
3338
DeduplicateByDef string `gorm:"column:rule_deduplicate_by_def"`
3439

@@ -38,4 +43,5 @@ type UtmCorrelationRules struct {
3843
DataTypes []UtmDataTypes `gorm:"many2many:utm_group_rules_data_type;joinForeignKey:rule_id;joinReferences:data_type_id"`
3944
}
4045

46+
4147
func (UtmCorrelationRules) TableName() string { return "utm_correlation_rules" }

backend/modules/eventprocessing/dto/correlation_rule.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type RuleDataTypeResponse struct {
2323
SystemOwner bool `json:"systemOwner"`
2424
}
2525

26+
2627
type CreateCorrelationRuleRequest struct {
2728
// JSON tags match Java UtmCorrelationRulesDTO field names for wire compatibility.
2829
RuleName string `json:"name"`
@@ -38,15 +39,17 @@ type CreateCorrelationRuleRequest struct {
3839

3940
RuleReferencesDef json.RawMessage `json:"references"`
4041
RuleDefinitionDef json.RawMessage `json:"definition"`
41-
AfterEventsDef json.RawMessage `json:"afterEvents"`
4242
RuleGroupByDef json.RawMessage `json:"groupBy"`
4343
DeduplicateByDef json.RawMessage `json:"deduplicateBy"`
4444

4545
RuleActive bool `json:"ruleActive"`
46+
CorrelationDef json.RawMessage `json:"correlation"`
4647

4748
DataTypes []DataTypeRef `json:"dataTypes"`
4849
}
4950

51+
52+
5053
type UpdateCorrelationRuleRequest struct {
5154
// RelPath identifies the rule to update (the YAML-direct identity).
5255
RelPath string `json:"relPath"`
@@ -64,15 +67,16 @@ type UpdateCorrelationRuleRequest struct {
6467

6568
RuleReferencesDef json.RawMessage `json:"references"`
6669
RuleDefinitionDef json.RawMessage `json:"definition"`
67-
AfterEventsDef json.RawMessage `json:"afterEvents"`
6870
RuleGroupByDef json.RawMessage `json:"groupBy"`
6971
DeduplicateByDef json.RawMessage `json:"deduplicateBy"`
7072

7173
RuleActive bool `json:"ruleActive"`
74+
CorrelationDef json.RawMessage `json:"correlation"`
7275

7376
DataTypes []DataTypeRef `json:"dataTypes"`
7477
}
7578

79+
7680
type CorrelationRuleResponse struct {
7781
// RelPath is the rule identity (replaces the legacy numeric id).
7882
RelPath string `json:"relPath"`
@@ -90,7 +94,7 @@ type CorrelationRuleResponse struct {
9094

9195
RuleReferencesDef json.RawMessage `json:"references"`
9296
RuleDefinitionDef json.RawMessage `json:"definition"`
93-
AfterEventsDef json.RawMessage `json:"afterEvents"`
97+
CorrelationDef json.RawMessage `json:"correlation"`
9498
RuleGroupByDef json.RawMessage `json:"groupBy"`
9599
DeduplicateByDef json.RawMessage `json:"deduplicateBy"`
96100

backend/modules/eventprocessing/module.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,5 @@ func (m *Module) GetIngestionStatsUsecase() connectors.IngestionStatsUsecase {
126126
}
127127

128128
func (m *Module) AfterEventsByRuleName(name string) (json.RawMessage, bool) {
129-
return m.ruleStore.AfterEventsByName(name)
129+
return m.ruleStore.CorrelationsByName(name)
130130
}

backend/modules/eventprocessing/usecase/correlation_rule.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,21 @@ func NewCorrelationRuleUsecase(store *RuleStore) connectors.CorrelationRuleUseca
2424
return &correlationRuleUsecase{store: store}
2525
}
2626

27+
28+
2729
func (u *correlationRuleUsecase) Create(_ context.Context, req dto.CreateCorrelationRuleRequest) error {
2830
if len(req.DataTypes) == 0 {
2931
return domain.ErrDataTypesRequired
3032
}
31-
if err := validateRuleContent(req.RuleDefinitionDef, req.AfterEventsDef); err != nil {
33+
34+
correlate:= req.CorrelationDef
35+
36+
if err := validateRuleContent(req.RuleDefinitionDef,correlate ); err != nil {
3237
return err
3338
}
3439
rule := buildRule(req.RuleName, req.RuleAdversary, req.RuleConfidentiality, req.RuleIntegrity,
3540
req.RuleAvailability, req.RuleCategory, req.RuleTechnique, req.RuleDescription,
36-
req.RuleReferencesDef, req.RuleDefinitionDef, req.AfterEventsDef, req.RuleGroupByDef,
41+
req.RuleReferencesDef, req.RuleDefinitionDef, correlate, req.RuleGroupByDef,
3742
req.DeduplicateByDef, req.DataTypes)
3843

3944
created, err := u.store.Create(rule)
@@ -105,10 +110,10 @@ func validateImportedRule(r Rule) error {
105110
if strings.TrimSpace(r.Where) == "" {
106111
return domain.ErrCorrelationRuleNullDefinition
107112
}
108-
if r.AfterEvents == nil {
113+
if r.Correlation == nil {
109114
return nil
110115
}
111-
raw, err := json.Marshal(r.AfterEvents)
116+
raw, err := json.Marshal(r.Correlation)
112117
if err != nil {
113118
return fmt.Errorf("%w: afterEvents is not serializable", domain.ErrCorrelationRuleInvalidContent)
114119
}
@@ -131,12 +136,15 @@ func (u *correlationRuleUsecase) Update(_ context.Context, req dto.UpdateCorrela
131136
if len(req.DataTypes) == 0 {
132137
return domain.ErrDataTypesRequired
133138
}
134-
if err := validateRuleContent(req.RuleDefinitionDef, req.AfterEventsDef); err != nil {
139+
140+
correlate:= req.CorrelationDef
141+
142+
if err := validateRuleContent(req.RuleDefinitionDef, correlate); err != nil {
135143
return err
136144
}
137145
rule := buildRule(req.RuleName, req.RuleAdversary, req.RuleConfidentiality, req.RuleIntegrity,
138146
req.RuleAvailability, req.RuleCategory, req.RuleTechnique, req.RuleDescription,
139-
req.RuleReferencesDef, req.RuleDefinitionDef, req.AfterEventsDef, req.RuleGroupByDef,
147+
req.RuleReferencesDef, req.RuleDefinitionDef, correlate, req.RuleGroupByDef,
140148
req.DeduplicateByDef, req.DataTypes)
141149

142150
if _, err := u.store.Update(req.RelPath, rule); err != nil {
@@ -210,7 +218,7 @@ func buildRule(name, adversary string, conf, integ, avail int, category, techniq
210218
Impact: Impact{Confidentiality: conf, Integrity: integ, Availability: avail},
211219
Where: rawToWhere(def),
212220
References: rawToAnySlice(refs),
213-
AfterEvents: rawToAny(after),
221+
Correlation: rawToAny(after),
214222
GroupBy: rawToStrSlice(groupBy),
215223
DeduplicateBy: rawToStrSlice(dedup),
216224
}
@@ -237,7 +245,7 @@ func storedToResponse(sr *StoredRule) *dto.CorrelationRuleResponse {
237245
RuleDescription: sr.Description,
238246
RuleReferencesDef: anyToRaw(sr.References),
239247
RuleDefinitionDef: anyToRaw(sr.Where),
240-
AfterEventsDef: anyToRaw(sr.AfterEvents),
248+
CorrelationDef: anyToRaw(sr.Correlation),
241249
RuleGroupByDef: anyToRaw(sr.GroupBy),
242250
DeduplicateByDef: anyToRaw(sr.DeduplicateBy),
243251
RuleLastUpdate: &mod,

backend/modules/eventprocessing/usecase/rule_bootstrap.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ func legacyToRule(row *domain.UtmCorrelationRules) Rule {
264264
for _, dt := range row.DataTypes {
265265
names = append(names, dt.DataType)
266266
}
267+
268+
267269
return Rule{
268270
Name: row.RuleName,
269271
Adversary: row.RuleAdversary,
@@ -274,7 +276,7 @@ func legacyToRule(row *domain.UtmCorrelationRules) Rule {
274276
Impact: Impact{Confidentiality: row.RuleConfidentiality, Integrity: row.RuleIntegrity, Availability: row.RuleAvailability},
275277
Where: rawToWhere(toRaw(row.RuleDefinitionDef)),
276278
References: rawToAnySlice(toRaw(row.RuleReferencesDef)),
277-
AfterEvents: rawToAny(toRaw(row.AfterEventsDef)),
279+
Correlation: rawToAny(toRaw(row.AfterEventsDef)),
278280
GroupBy: rawToStrSlice(toRaw(row.RuleGroupByDef)),
279281
DeduplicateBy: rawToStrSlice(toRaw(row.DeduplicateByDef)),
280282
}

backend/modules/eventprocessing/usecase/rule_model.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type Rule struct {
3838
References []any `yaml:"references,omitempty"`
3939
Description string `yaml:"description,omitempty"`
4040
Where string `yaml:"where"`
41-
AfterEvents any `yaml:"afterEvents,omitempty"`
41+
Correlation any `yaml:"correlation,omitempty"`
4242
GroupBy []string `yaml:"groupBy,omitempty"`
4343
DeduplicateBy []string `yaml:"deduplicateBy,omitempty"`
4444
}

backend/modules/eventprocessing/usecase/rule_store.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,12 @@ func (s *RuleStore) FindByName(name string) *StoredRule {
162162
return nil
163163
}
164164

165-
func (s *RuleStore) AfterEventsByName(name string) (json.RawMessage, bool) {
165+
func (s *RuleStore) CorrelationsByName(name string) (json.RawMessage, bool) {
166166
sr := s.FindByName(name)
167167
if sr == nil {
168168
return nil, false
169169
}
170-
return anyToRaw(sr.AfterEvents), true
170+
return anyToRaw(sr.Correlation), true
171171
}
172172

173173
// List applies the filter, sorts by name and paginates. It returns the page

frontend/src/features/alerting-rules/components/rule-form.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface RuleFormState {
3535
ruleActive: boolean
3636
dataTypes: string[]
3737
definition: string
38-
afterEvents: AfterStep[]
38+
correlation: AfterStep[]
3939
groupBy: string[]
4040
deduplicateBy: string[]
4141
references: string[]
@@ -59,9 +59,9 @@ function parseConditions(w: unknown): Condition[] {
5959
return { field: String(o.field ?? ''), operator: String(o.operator ?? 'filter_term'), value: valueToStr(o.value) }
6060
})
6161
}
62-
function parseSteps(after: unknown): AfterStep[] {
63-
if (!Array.isArray(after)) return []
64-
return after.map((s) => {
62+
function parseSteps(raw: unknown): AfterStep[] {
63+
if (!Array.isArray(raw)) return []
64+
return raw.map((s) => {
6565
const o = (s ?? {}) as Record<string, unknown>
6666
return {
6767
indexPattern: String(o.indexPattern ?? ''),
@@ -86,7 +86,7 @@ export function ruleToForm(r?: CorrelationRule): RuleFormState {
8686
ruleActive: r?.ruleActive ?? true,
8787
dataTypes: (r?.dataTypes ?? []).filter((d) => d.included).map((d) => d.dataType),
8888
definition: r?.definition ?? '',
89-
afterEvents: parseSteps(r?.afterEvents),
89+
correlation: parseSteps(r?.correlation ?? r?.afterEvents),
9090
groupBy: strArray(r?.groupBy),
9191
deduplicateBy: strArray(r?.deduplicateBy),
9292
references: strArray(r?.references),
@@ -122,7 +122,7 @@ export function formToInput(f: RuleFormState, relPath?: string): SaveRuleInput {
122122
description: f.description.trim(),
123123
references: f.references.filter(Boolean),
124124
definition: f.definition,
125-
afterEvents: stepsToJson(f.afterEvents),
125+
correlation: stepsToJson(f.correlation),
126126
groupBy: f.groupBy.filter(Boolean),
127127
deduplicateBy: f.deduplicateBy.filter(Boolean),
128128
ruleActive: f.ruleActive,
@@ -134,7 +134,7 @@ export function formToInput(f: RuleFormState, relPath?: string): SaveRuleInput {
134134

135135
export function RuleForm({ form, setForm, dataTypeOptions, t }: { form: RuleFormState; setForm: Dispatch<SetStateAction<RuleFormState>>; dataTypeOptions: DataTypeOption[]; t: TFunction }) {
136136
const set = <K extends keyof RuleFormState>(k: K, v: RuleFormState[K]) => setForm((f) => ({ ...f, [k]: v }))
137-
const setSteps = (steps: AfterStep[]) => set('afterEvents', steps)
137+
const setSteps = (steps: AfterStep[]) => set('correlation', steps)
138138

139139
// `where` condition: visual builder ⇄ raw CEL. Visual is the source when it
140140
// 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
221221
)}
222222
</div>
223223

224-
<Section title={t('alertingRules.editor.afterEvents')}>
225-
<p className="mb-3 text-[11px] text-muted-foreground">{t('alertingRules.editor.afterEventsHint')}</p>
224+
<Section title={t('alertingRules.editor.correlationSteps')}>
225+
<p className="mb-3 text-[11px] text-muted-foreground">{t('alertingRules.editor.correlationStepsHint')}</p>
226226
<div className="space-y-3">
227-
{form.afterEvents.map((step, i) => (
227+
{form.correlation.map((step, i) => (
228228
<StepCard
229229
key={i}
230230
step={step}
231231
index={i}
232232
t={t}
233-
onChange={(s) => setSteps(form.afterEvents.map((x, idx) => (idx === i ? s : x)))}
234-
onRemove={() => setSteps(form.afterEvents.filter((_, idx) => idx !== i))}
233+
onChange={(s) => setSteps(form.correlation.map((x, idx) => (idx === i ? s : x)))}
234+
onRemove={() => setSteps(form.correlation.filter((_, idx) => idx !== i))}
235235
/>
236236
))}
237237
<button
238-
onClick={() => setSteps([...form.afterEvents, { indexPattern: 'v11-log-*', within: '2m', count: 1, with: [], or: [] }])}
238+
onClick={() => setSteps([...form.correlation, { indexPattern: 'v11-log-*', within: '2m', count: 1, with: [], or: [] }])}
239239
className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-border py-2 text-xs text-muted-foreground hover:border-primary/40 hover:text-foreground"
240240
>
241241
<Plus size={13} /> {t('alertingRules.editor.addStep')}
@@ -256,7 +256,7 @@ export function RuleForm({ form, setForm, dataTypeOptions, t }: { form: RuleForm
256256
)
257257
}
258258

259-
/* ─── afterEvents builder pieces ───────────────────────────────────────── */
259+
/* ─── correlation steps builder pieces ────────────────────────────────── */
260260

261261
function StepCard({ step, index, onChange, onRemove, t }: { step: AfterStep; index: number; onChange: (s: AfterStep) => void; onRemove: () => void; t: TFunction }) {
262262
const patch = (p: Partial<AfterStep>) => onChange({ ...step, ...p })

frontend/src/features/alerting-rules/lib/rule-yaml.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function ruleFormToYaml(f: RuleFormState): string {
7575
if (f.description) doc.description = f.description
7676
doc.where = f.definition
7777

78-
const steps = f.afterEvents
78+
const steps = f.correlation
7979
.filter((s) => s.indexPattern.trim())
8080
.map((s) => {
8181
const step: Record<string, unknown> = {
@@ -88,7 +88,7 @@ export function ruleFormToYaml(f: RuleFormState): string {
8888
if (ors.length) step.or = ors.map((g) => ({ with: condsToYaml(g.with) }))
8989
return step
9090
})
91-
if (steps.length) doc.afterEvents = steps
91+
if (steps.length) doc.correlation = steps
9292
if (f.groupBy.length) doc.groupBy = f.groupBy
9393
if (f.deduplicateBy.length) doc.deduplicateBy = f.deduplicateBy
9494

@@ -126,7 +126,7 @@ export function yamlToRuleForm(content: string): RuleParseResult {
126126
ruleActive: doc.active !== false,
127127
dataTypes: strArray(doc.dataTypes),
128128
definition: str(where),
129-
afterEvents: parseSteps(doc.afterEvents),
129+
correlation: parseSteps(doc.correlation ?? doc.afterEvents),
130130
groupBy: strArray(doc.groupBy),
131131
deduplicateBy: strArray(doc.deduplicateBy),
132132
references: strArray(doc.references),

frontend/src/features/alerting-rules/pages/AlertingRulesPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ function RuleDrawer({
529529
}
530530

531531
function RuleView({ rule, df, t }: { rule: CorrelationRule; df: ReturnType<typeof useDateFormat>; t: TFunction }) {
532-
const steps = ruleToForm(rule).afterEvents
532+
const steps = ruleToForm(rule).correlation
533533
return (
534534
<div className="space-y-4">
535535
{rule.description && <Section title={t('alertingRules.view.description')}><RuleDescription text={rule.description} /></Section>}
@@ -547,7 +547,7 @@ function RuleView({ rule, df, t }: { rule: CorrelationRule; df: ReturnType<typeo
547547
<DefinitionView definition={rule.definition} t={t} />
548548
</Section>
549549
{steps.length > 0 && (
550-
<Section title={t('alertingRules.view.afterEvents')}>
550+
<Section title={t('alertingRules.view.correlationSteps')}>
551551
<AfterEventsView steps={steps} t={t} />
552552
</Section>
553553
)}
@@ -607,7 +607,7 @@ function CelNodeView({ node, t, depth }: { node: CelNode; t: TFunction; depth: n
607607
}
608608

609609
/** Read-only cards for the correlation (after-events) steps. */
610-
function AfterEventsView({ steps, t }: { steps: ReturnType<typeof ruleToForm>['afterEvents']; t: TFunction }) {
610+
function AfterEventsView({ steps, t }: { steps: ReturnType<typeof ruleToForm>['correlation']; t: TFunction }) {
611611
return (
612612
<div className="space-y-2">
613613
{steps.map((s, i) => (

0 commit comments

Comments
 (0)