mihari 7.6.2 → 7.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5fff977863ca54dae5b645e3587d8f09f0583df44fca258ac4f1c8f3455a74e
4
- data.tar.gz: 3c45cf5405737aaddbd79b42590509f155b3be13c3a0645057cc446666728655
3
+ metadata.gz: 12856ce5fee6e623a5579cab99a03629c611db1f65e087b04167cae69f870647
4
+ data.tar.gz: 28577ad798c7033ead6aaeced545a9401f17d0a31d29551950c3be583bc107ea
5
5
  SHA512:
6
- metadata.gz: d189d79161738b06e2ee970b835b953bd968c4c4500b7cf589fcf3c2ac31a49f46a9d2fcf3581818484f9daf0b72a87a1d3cd39084d39e4a9bcfdf4ab0edc1a4
7
- data.tar.gz: 463d12e871297b9d5be403139720fc51d09bd51e0c41855d47cbe889de086f2a5a2d2fe25fa592f81ddec1b6e56195b9878372c1fc8955f23357b72d09bbdce0
6
+ metadata.gz: 68d780eca473d2b081c3c9bb7a60c9c5bfa371a0c6e08a307ceaa3138f5097b70606902e657aab22d1013a5033904bd7135595812d76a0554bde38f3b75917b6
7
+ data.tar.gz: 6b0ab8b458109b4c496298560772bd44e845a96c97ab89ec3191416855a66c4de92277d66d63607b17a857d35be10343adbdd71183616f64e7b984b617cde32a
data/Rakefile CHANGED
@@ -40,9 +40,7 @@ def build_swagger_doc(path)
40
40
  recursive_delete json, key
41
41
  end
42
42
 
43
- f = File.open(path, "w")
44
- f.write json.to_yaml
45
- f.close
43
+ File.write(path, json.to_yaml)
46
44
  end
47
45
 
48
46
  namespace :build do
@@ -26,14 +26,37 @@ module Mihari
26
26
  end
27
27
  end
28
28
 
29
- desc "validate PATH", "Validate a rule"
29
+ desc "validate PATH", "Validate rule(s)"
30
30
  #
31
- # Validate format of a rule
31
+ # Validate rule(s)
32
+ #
33
+ # @param [Array<String>] paths
34
+ #
35
+ def validate(*paths)
36
+ # @type [Array<Mihari::ValidationError>]
37
+ errors = paths.flat_map { |path| Dir.glob(path) }.map do |path|
38
+ Dry::Monads::Try[ValidationError] { Mihari::Rule.from_file(path) }
39
+ end.filter_map do |result|
40
+ result.exception if result.error?
41
+ end
42
+ return if errors.empty?
43
+
44
+ errors.each do |error|
45
+ data = Entities::ErrorMessage.represent(message: error.message, detail: error.detail)
46
+ warn JSON.pretty_generate(data.as_json)
47
+ end
48
+
49
+ exit 1
50
+ end
51
+
52
+ desc "format PATH", "format a rule"
53
+ #
54
+ # Format a rule file
32
55
  #
33
56
  # @param [String] path
34
57
  #
35
- def validate(path)
36
- rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.from_yaml File.read(path) }.value!
58
+ def format(path)
59
+ rule = Dry::Monads::Try[ValidationError] { Mihari::Rule.from_file(path) }.value!
37
60
  puts rule.data.to_yaml
38
61
  end
39
62
 
data/lib/mihari/config.rb CHANGED
@@ -40,6 +40,7 @@ module Mihari
40
40
  zoomeye_api_key: nil,
41
41
  # sidekiq
42
42
  sidekiq_redis_url: nil,
43
+ sidekiq_retry: 0,
43
44
  # others
44
45
  hide_config_values: true,
45
46
  ignore_error: false,
@@ -174,6 +175,9 @@ module Mihari
174
175
  # @!attribute [r] sidekiq_redis_url
175
176
  # @return [URI, nil]
176
177
 
178
+ # @!attribute [r] sidekiq_retry
179
+ # @return [Integer]
180
+
177
181
  def database_url=(val)
178
182
  super(URI(val.to_s))
179
183
  end
@@ -6,7 +6,7 @@ ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym "CPE" }
6
6
  #
7
7
  # Mihari v7 DB schema
8
8
  #
9
- class V7Schema < ActiveRecord::Migration[7.1]
9
+ class V7Schema < ActiveRecord::Migration[7.2]
10
10
  def change
11
11
  create_table :rules, id: :string, if_not_exists: true do |t|
12
12
  t.string :title, null: false
@@ -16,7 +16,9 @@ module Mihari
16
16
  def call(artifact)
17
17
  return if artifact.domain.nil?
18
18
 
19
- artifact.whois_record ||= memoized_lookup(PublicSuffix.domain(artifact.domain))
19
+ artifact.tap do |tapped|
20
+ tapped.whois_record ||= memoized_lookup(PublicSuffix.domain(artifact.domain))
21
+ end
20
22
  end
21
23
 
22
24
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ostruct"
4
+
3
5
  module Mihari
4
6
  module Models
5
7
  #
@@ -158,10 +160,7 @@ module Mihari
158
160
  # @return [Boolean] true if it is unique. Otherwise false.
159
161
  #
160
162
  def unique?(base_time: nil, artifact_ttl: nil)
161
- artifact = self.class.joins(:alert).where(
162
- data:,
163
- alert: {rule_id:}
164
- ).order(created_at: :desc).first
163
+ artifact = self.class.joins(:alert).where(data:, alert: {rule_id:}).order(created_at: :desc).first
165
164
  return true if artifact.nil?
166
165
 
167
166
  # check whether the artifact is decayed or not
@@ -179,7 +178,32 @@ module Mihari
179
178
  end
180
179
 
181
180
  def enrich
182
- callable_enrichers.each { |enricher| enricher.result self }
181
+ enrich_by_enrichers callable_enrichers
182
+ end
183
+
184
+ #
185
+ # @param [Array<Mihari::Enrichers::Base>] enrichers
186
+ # @param [Boolean] parallel
187
+ #
188
+ # @return [Mihari::Models::Artifact]
189
+ #
190
+ def enrich_by_enrichers(enrichers)
191
+ # NOTE: doing parallel with ActiveRecord objects is troublesome (e.g. connection issue, etc.)
192
+ # so converting the object to an OpenStruct object
193
+ s = struct
194
+ results = Parallel.map(enrichers) { |enricher| enricher.result s }
195
+ enriched = results.compact.map { |result| result.value_or(nil) }.compact
196
+
197
+ self.dns_records = enriched.map(&:dns_records).flatten.compact
198
+ self.cpes = enriched.map(&:cpes).flatten.compact
199
+ self.ports = enriched.map(&:ports).flatten.compact
200
+ self.vulnerabilities = enriched.map(&:vulnerabilities).flatten.compact
201
+
202
+ self.autonomous_system = enriched.map(&:autonomous_system).compact.first
203
+ self.geolocation = enriched.map(&:geolocation).compact.first
204
+ self.whois_record = enriched.map(&:whois_record).compact.first
205
+
206
+ self
183
207
  end
184
208
 
185
209
  #
@@ -195,6 +219,18 @@ module Mihari
195
219
  end
196
220
  end
197
221
 
222
+ def struct
223
+ OpenStruct.new(attributes).tap do |s|
224
+ s.domain = domain
225
+ s.cpes ||= []
226
+ s.dns_records ||= []
227
+ s.ports ||= []
228
+ s.reverse_dns_names ||= []
229
+ s.vulnerabilities ||= []
230
+ s.tags ||= []
231
+ end
232
+ end
233
+
198
234
  class << self
199
235
  # @!method search_by_filter(filter)
200
236
  # @param [Mihari::Structs::Filters::Search] filter
@@ -212,7 +248,7 @@ module Mihari
212
248
  #
213
249
  def callable_enrichers
214
250
  @callable_enrichers ||= Mihari.enrichers.map(&:new).select do |enricher|
215
- enricher.callable?(self)
251
+ enricher.callable? self
216
252
  end
217
253
  end
218
254
 
data/lib/mihari/rule.rb CHANGED
@@ -5,6 +5,9 @@ module Mihari
5
5
  include Concerns::FalsePositiveNormalizable
6
6
  include Concerns::FalsePositiveValidatable
7
7
 
8
+ # @return [String, nil]
9
+ attr_reader :path_or_id
10
+
8
11
  # @return [Hash]
9
12
  attr_reader :data
10
13
 
@@ -19,9 +22,11 @@ module Mihari
19
22
  #
20
23
  # @param [Hash] data
21
24
  #
22
- def initialize(**data)
25
+ # @param [Object] path_or_id
26
+ def initialize(path_or_id: nil, **data)
23
27
  super()
24
28
 
29
+ @path_or_id = path_or_id
25
30
  @data = data.deep_symbolize_keys
26
31
  @errors = nil
27
32
  @base_time = Time.now.utc
@@ -173,10 +178,7 @@ module Mihari
173
178
  #
174
179
  def enriched_artifacts
175
180
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
176
- artifact.tap do |tapped|
177
- # NOTE: To apply changes correctly, enrichers should be applied to an artifact serially
178
- enrichers.each { |enricher| enricher.result(tapped) }
179
- end
181
+ artifact.enrich_by_enrichers enrichers
180
182
  end
181
183
  end
182
184
 
@@ -251,16 +253,29 @@ module Mihari
251
253
  end
252
254
 
253
255
  class << self
256
+ #
257
+ # Load rule from YAML file
258
+ #
259
+ # @param [String] path
260
+ #
261
+ # @return [Mihari::Rule]
262
+ #
263
+ def from_file(path)
264
+ yaml = File.read(path)
265
+ from_yaml(yaml, path: path)
266
+ end
267
+
254
268
  #
255
269
  # Load rule from YAML string
256
270
  #
257
271
  # @param [String] yaml
272
+ # @param [String, nil] path
258
273
  #
259
274
  # @return [Mihari::Rule]
260
275
  #
261
- def from_yaml(yaml)
276
+ def from_yaml(yaml, path: nil)
262
277
  data = YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
263
- new(**data)
278
+ new(path_or_id: path, **data)
264
279
  end
265
280
 
266
281
  #
@@ -269,7 +284,7 @@ module Mihari
269
284
  # @return [Mihari::Rule]
270
285
  #
271
286
  def from_model(model)
272
- new(**model.data)
287
+ new(path_or_id: model.id, **model.data)
273
288
  end
274
289
  end
275
290
 
@@ -409,7 +424,7 @@ module Mihari
409
424
  @data = result.to_h
410
425
  @errors = result.errors
411
426
 
412
- raise ValidationError.new("Validation failed", errors) if errors?
427
+ raise ValidationError.new("#{path_or_id}: validation failed", errors) if errors?
413
428
  end
414
429
  end
415
430
  end
@@ -6,6 +6,7 @@ require "mihari/sidekiq/jobs"
6
6
 
7
7
  Sidekiq.configure_server do |config|
8
8
  config.redis = {url: Mihari.config.sidekiq_redis_url.to_s}
9
+ config.default_job_options = {retry: Mihari.config.sidekiq_retry}
9
10
  end
10
11
 
11
12
  Sidekiq.configure_client do |config|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "7.6.2"
4
+ VERSION = "7.6.4"
5
5
  end