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(path_or_id: nil, **data) ⇒ Rule

Initialize

Parameters:

  • data (Hash)
  • path_or_id (Object) (defaults to: nil)


26
27
28
29
30
31
32
33
34
35
# File 'lib/mihari/rule.rb', line 26

def initialize(path_or_id: nil, **data)
  super()

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

  validate!
end

Instance Attribute Details

#base_timeTime (readonly)

Returns:

  • (Time)


18
19
20
# File 'lib/mihari/rule.rb', line 18

def base_time
  @base_time
end

#dataHash (readonly)

Returns:

  • (Hash)


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

def data
  @data
end

#errorsArray? (readonly)

Returns:

  • (Array, nil)


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

def errors
  @errors
end

#path_or_idString? (readonly)

Returns:

  • (String, nil)


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

def path_or_id
  @path_or_id
end

Class Method Details

.from_file(path) ⇒ Mihari::Rule

Load rule from YAML file

Parameters:

  • path (String)

Returns:



263
264
265
266
# File 'lib/mihari/rule.rb', line 263

def from_file(path)
  yaml = File.read(path)
  from_yaml(yaml, path: path)
end

.from_model(model) ⇒ Mihari::Rule

Parameters:

Returns:



286
287
288
# File 'lib/mihari/rule.rb', line 286

def from_model(model)
  new(path_or_id: model.id, **model.data)
end

.from_yaml(yaml, path: nil) ⇒ Mihari::Rule

Load rule from YAML string

Parameters:

  • yaml (String)
  • path (String, nil) (defaults to: nil)

Returns:



276
277
278
279
# File 'lib/mihari/rule.rb', line 276

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

Instance Method Details

#[](key) ⇒ Object



46
47
48
# File 'lib/mihari/rule.rb', line 46

def [](key)
  data key.to_sym
end

#artifact_ttlInteger?

Returns:

  • (Integer, nil)


132
133
134
# File 'lib/mihari/rule.rb', line 132

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:



141
142
143
144
145
146
147
148
# File 'lib/mihari/rule.rb', line 141

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:



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

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:



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

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)


95
96
97
# File 'lib/mihari/rule.rb', line 95

def created_on
  data[:created_on]
end

#data_typesArray<String>

Returns:

  • (Array<String>)


88
89
90
# File 'lib/mihari/rule.rb', line 88

def data_types
  data[:data_types]
end

#descriptionString

Returns:

  • (String)


67
68
69
# File 'lib/mihari/rule.rb', line 67

def description
  data[:description]
end

#diff?Boolean

Returns:

  • (Boolean)


237
238
239
240
241
242
# File 'lib/mihari/rule.rb', line 237

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:



179
180
181
182
183
# File 'lib/mihari/rule.rb', line 179

def enriched_artifacts
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
    artifact.enrich_by_enrichers enrichers
  end
end

#errors?Boolean

Returns:

  • (Boolean)


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

def errors?
  return false if errors.nil?

  !errors.empty?
end

#exists?Boolean

Returns:

  • (Boolean)


247
248
249
# File 'lib/mihari/rule.rb', line 247

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

#falsepositivesArray<String, RegExp>

Returns:

  • (Array<String, RegExp>)


125
126
127
# File 'lib/mihari/rule.rb', line 125

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

#idString

Returns:

  • (String)


53
54
55
# File 'lib/mihari/rule.rb', line 53

def id
  data[:id]
end

#modelMihari::Models::Rule



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

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:



159
160
161
162
163
# File 'lib/mihari/rule.rb', line 159

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


81
82
83
# File 'lib/mihari/rule.rb', line 81

def queries
  data[:queries]
end

#taggingsArray<Mihari::Models::Tagging>

Returns:



118
119
120
# File 'lib/mihari/rule.rb', line 118

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

#tagsArray<Mihari::Models::Tag>

Returns:



109
110
111
112
113
# File 'lib/mihari/rule.rb', line 109

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

#titleString

Returns:

  • (String)


60
61
62
# File 'lib/mihari/rule.rb', line 60

def title
  data[:title]
end

#unique_artifactsArray<Mihari::Models::Artifact>

Uniquify artifacts (assure rule level uniqueness)

Returns:



170
171
172
# File 'lib/mihari/rule.rb', line 170

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

#update_or_createObject



251
252
253
# File 'lib/mihari/rule.rb', line 251

def update_or_create
  model.save
end

#updated_onDate?

Returns:

  • (Date, nil)


102
103
104
# File 'lib/mihari/rule.rb', line 102

def updated_on
  data[:updated_on]
end

#yamlString

Returns:

  • (String)


74
75
76
# File 'lib/mihari/rule.rb', line 74

def yaml
  data.deep_stringify_keys.to_yaml
end