Skip to content

Commit 1dc326e

Browse files
Merge branch 'release/v12.0.0' into backlog/v12_integration_index
Signed-off-by: Alex Sánchez <alex.sanchez@utmstack.com>
2 parents 89c0766 + 46d18d9 commit 1dc326e

24 files changed

Lines changed: 184 additions & 80 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

backend/modules/integrations/usecase/module.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ func (u *moduleUsecase) Create(ctx context.Context, req dto.CreateModuleRequest)
167167
}
168168

169169
if _,err:= u.opensearch.Create(ctx,os_dto.CreateIndexPatternRequest{
170-
PatternModule: &req.DataType,
170+
Pattern: req.DataType,
171+
PatternModule: &req.ModuleName,
171172
});err!=nil{
172173
//opensearch insertion fails, rolling back database
173174
if err:=u.repo.Delete(ctx,m.ID);err!=nil{

backend/modules/opensearch/usecase/index_pattern.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ func (u *indexPatternUsecase) Create(ctx context.Context, req dto.CreateIndexPat
4949
return nil, errInvalidPattern
5050
}
5151

52+
if !strings.HasPrefix(req.Pattern,constants.CUSTOM_INDEX_PREFIX) {
53+
normalized := strings.ReplaceAll(strings.Trim(strings.ToLower(req.Pattern), ""), " ", "-")
54+
req.Pattern = fmt.Sprintf("%s-%s-*", constants.CUSTOM_INDEX_PREFIX, normalized)
55+
}
56+
57+
5258
p := &domain.UtmIndexPattern{
5359
Pattern: req.Pattern,
5460
PatternModule: req.PatternModule,

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 })

0 commit comments

Comments
 (0)