Class: Mihari::Rule

Inherits:
Service show all
Includes:
Concerns::FalsePositiveNormalizable, Concerns::FalsePositiveValidatable
Defined in:
lib/mihari/rule.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Concerns::FalsePositiveValidatable

#valid_falsepositive?

Methods included from Concerns::FalsePositiveNormalizable

#normalize_falsepositive

Methods inherited from Service

call, #result, result

Constructor Details

#initialize(**data) ⇒ Rule

Initialize

Parameters:

  • data (Hash)


22
23
24
25
26
27
28
29
30
# File 'lib/mihari/rule.rb', line 22

def initialize(**data)
  super()

  @data = data.deep_symbolize_keys
  @errors = nil
  @base_time = Time.now.utc

  validate!
end

Instance Attribute Details

#base_timeTime (readonly)

Returns:

  • (Time)


15
16
17
# File 'lib/mihari/rule.rb', line 15

def base_time
  @base_time
end

#dataHash (readonly)

Returns:

  • (Hash)


9
10
11
# File 'lib/mihari/rule.rb', line 9

def data
  @data
end

#errorsArray? (readonly)

Returns:

  • (Array, nil)


12
13
14
# File 'lib/mihari/rule.rb', line 12

def errors
  @errors
end

Class Method Details

.from_model(model) ⇒ Mihari::Rule

Parameters:

Returns:



271
272
273
# File 'lib/mihari/rule.rb', line 271

def from_model(model)
  new(**model.data)
end

.from_yaml(yaml) ⇒ Mihari::Rule

Load rule from YAML string

Parameters:

  • yaml (String)

Returns:



261
262
263
264
# File 'lib/mihari/rule.rb', line 261

def from_yaml(yaml)
  data = YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
  new(**data)
end

Instance Method Details

#[](key) ⇒ Object



41
42
43
# File 'lib/mihari/rule.rb', line 41

def [](key)
  data key.to_sym
end

#artifact_ttlInteger?

Returns:

  • (Integer, nil)


127
128
129
# File 'lib/mihari/rule.rb', line 127

def artifact_ttl
  data[:artifact_ttl]
end

#artifactsArray<Mihari::Models::Artifact>

Returns a list of artifacts matched with queries/analyzers (with the rule ID)

Returns:



136
137
138
139
140
141
142
143
# File 'lib/mihari/rule.rb', line 136

def artifacts
  analyzer_results.flat_map do |result|
    artifacts = result.value!
    artifacts.map do |artifact|
      artifact.tap { |tapped| tapped.rule_id = id }
    end
  end
end

#bulk_emitArray<Mihari::Models::Alert>

Bulk emit

Returns:



188
189
190
191
192
193
194
195
# File 'lib/mihari/rule.rb', line 188

def bulk_emit
  return [] if enriched_artifacts.empty?

  [].tap do |out|
    out << serial_emitters.map { |emitter| emitter.result(enriched_artifacts).value_or(nil) }
    out << Parallel.map(parallel_emitters) { |emitter| emitter.result(enriched_artifacts).value_or(nil) }
  end.flatten.compact
end

#callMihari::Models::Alert?

Set artifacts & run emitters in parallel

Returns:



202
203
204
205
206
207
208
209
210
# File 'lib/mihari/rule.rb', line 202

def call
  # Validate analyzers & emitters before using them
  analyzers
  emitters

  alert_or_something = bulk_emit
  # Return Mihari::Models::Alert created by the database emitter
  alert_or_something.find { |res| res.is_a?(Mihari::Models::Alert) }
end

#created_onDate?

Returns:

  • (Date, nil)


90
91
92
# File 'lib/mihari/rule.rb', line 90

def created_on
  data[:created_on]
end

#data_typesArray<String>

Returns:

  • (Array<String>)


83
84
85
# File 'lib/mihari/rule.rb', line 83

def data_types
  data[:data_types]
end

#descriptionString

Returns:

  • (String)


62
63
64
# File 'lib/mihari/rule.rb', line 62

def description
  data[:description]
end

#diff?Boolean

Returns:

  • (Boolean)


235
236
237
238
239
240
# File 'lib/mihari/rule.rb', line 235

def diff?
  model = Mihari::Models::Rule.find(id)
  model.data != diff_comparable_data
rescue ActiveRecord::RecordNotFound
  false
end

#enriched_artifactsArray<Mihari::Models::Artifact>

Enriched artifacts

Returns:



174
175
176
177
178
179
180
181
# File 'lib/mihari/rule.rb', line 174

def enriched_artifacts
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
    artifact.tap do |tapped|
      # NOTE: To apply changes correctly, enrichers should be applied to an artifact serially
      enrichers.each { |enricher| enricher.result(tapped) }
    end
  end
end

#errors?Boolean

Returns:

  • (Boolean)


35
36
37
38
39
# File 'lib/mihari/rule.rb', line 35

def errors?
  return false if errors.nil?

  !errors.empty?
end

#exists?Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/mihari/rule.rb', line 245

def exists?
  Mihari::Models::Rule.exists? id
end

#falsepositivesArray<String, RegExp>

Returns:

  • (Array<String, RegExp>)


120
121
122
# File 'lib/mihari/rule.rb', line 120

def falsepositives
  @falsepositives ||= data[:falsepositives].map { |fp| normalize_falsepositive fp }
end

#idString

Returns:

  • (String)


48
49
50
# File 'lib/mihari/rule.rb', line 48

def id
  data[:id]
end

#modelMihari::Models::Rule



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/mihari/rule.rb', line 215

def model
  Mihari::Models::Rule.find(id).tap do |rule|
    rule.title = title
    rule.description = description
    rule.data = data
    rule.taggings = taggings
  end
rescue ActiveRecord::RecordNotFound
  Mihari::Models::Rule.new(
    id:,
    title:,
    description:,
    data:,
    taggings:
  )
end

#normalized_artifactsArray<Mihari::Models::Artifact>

Normalize artifacts

  • Reject invalid artifacts (for just in case)

  • Select artifacts with allowed data types

  • Reject artifacts with false positive values

  • Set rule ID

Returns:



154
155
156
157
158
# File 'lib/mihari/rule.rb', line 154

def normalized_artifacts
  valid_artifacts = artifacts.uniq(&:data).select(&:valid?)
  date_type_allowed_artifacts = valid_artifacts.select { |artifact| data_types.include? artifact.data_type }
  date_type_allowed_artifacts.reject { |artifact| falsepositive? artifact.data }
end

#queriesArray<Hash>

Returns:

  • (Array<Hash>)


76
77
78
# File 'lib/mihari/rule.rb', line 76

def queries
  data[:queries]
end

#taggingsArray<Mihari::Models::Tagging>

Returns:



113
114
115
# File 'lib/mihari/rule.rb', line 113

def taggings
  tags.map { |tag| Models::Tagging.find_or_create_by(tag_id: tag.id, rule_id: id) }
end

#tagsArray<Mihari::Models::Tag>

Returns:



104
105
106
107
108
# File 'lib/mihari/rule.rb', line 104

def tags
  data[:tags].uniq.filter_map do |name|
    Models::Tag.find_or_create_by(name:)
  end
end

#titleString

Returns:

  • (String)


55
56
57
# File 'lib/mihari/rule.rb', line 55

def title
  data[:title]
end

#unique_artifactsArray<Mihari::Models::Artifact>

Uniquify artifacts (assure rule level uniqueness)

Returns:



165
166
167
# File 'lib/mihari/rule.rb', line 165

def unique_artifacts
  normalized_artifacts.select { |artifact| artifact.unique?(base_time:, artifact_ttl:) }
end

#update_or_createObject



249
250
251
# File 'lib/mihari/rule.rb', line 249

def update_or_create
  model.save
end

#updated_onDate?

Returns:

  • (Date, nil)


97
98
99
# File 'lib/mihari/rule.rb', line 97

def updated_on
  data[:updated_on]
end

#yamlString

Returns:

  • (String)


69
70
71
# File 'lib/mihari/rule.rb', line 69

def yaml
  data.deep_stringify_keys.to_yaml
end