mihari 5.2.1 → 5.2.2

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/lib/mihari/analyzers/base.rb +20 -115
  4. data/lib/mihari/analyzers/censys.rb +20 -2
  5. data/lib/mihari/analyzers/onyphe.rb +1 -1
  6. data/lib/mihari/analyzers/rule.rb +116 -60
  7. data/lib/mihari/analyzers/shodan.rb +1 -1
  8. data/lib/mihari/analyzers/urlscan.rb +6 -9
  9. data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -5
  10. data/lib/mihari/cli/main.rb +2 -2
  11. data/lib/mihari/commands/search.rb +69 -0
  12. data/lib/mihari/mixins/error_notification.rb +0 -2
  13. data/lib/mihari/models/artifact.rb +1 -1
  14. data/lib/mihari/schemas/rule.rb +2 -17
  15. data/lib/mihari/structs/censys.rb +167 -11
  16. data/lib/mihari/structs/config.rb +28 -0
  17. data/lib/mihari/structs/google_public_dns.rb +39 -1
  18. data/lib/mihari/structs/greynoise.rb +93 -6
  19. data/lib/mihari/structs/ipinfo.rb +40 -0
  20. data/lib/mihari/structs/onyphe.rb +88 -6
  21. data/lib/mihari/structs/rule.rb +4 -2
  22. data/lib/mihari/structs/shodan.rb +138 -4
  23. data/lib/mihari/structs/urlscan.rb +98 -1
  24. data/lib/mihari/structs/virustotal_intelligence.rb +96 -1
  25. data/lib/mihari/version.rb +1 -1
  26. data/lib/mihari.rb +1 -0
  27. data/mihari.gemspec +8 -7
  28. metadata +29 -30
  29. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  30. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
  31. data/.github/workflows/test.yml +0 -90
  32. data/config/pre_commit.yml +0 -3
  33. data/docker/Dockerfile +0 -14
  34. data/examples/ipinfo_hosted_domains.rb +0 -45
  35. data/images/Tines-Full_Logo-Tines_Black.png +0 -0
  36. data/images/alert.png +0 -0
  37. data/images/logo.png +0 -0
  38. data/images/misp.png +0 -0
  39. data/images/overview.jpg +0 -0
  40. data/images/slack.png +0 -0
  41. data/images/tines.png +0 -0
  42. data/images/web_alerts.png +0 -0
  43. data/images/web_config.png +0 -0
  44. data/lib/mihari/commands/searcher.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ce7e22d3d54cc3e66fec62b494ce5f9def9107129513cd51803c71b8ae48b18
4
- data.tar.gz: fd85f33a666a77a8b43d12dc3234ab2656cd843c8f7e23025c1a5f076947fbcf
3
+ metadata.gz: 77de9f96ff14a64b2ab7a492fff36ed1515df7def7b18b5d81bac6785a5b75c0
4
+ data.tar.gz: 9e99189740e4e3b6ee6af97cc09fbd2af1f5395c047df925f528721f2c68595d
5
5
  SHA512:
6
- metadata.gz: a17809e3c6f52e7d37f6df2fc92b0ad5a1d134556e6ef13cb64b3802074a44f2ecd0c2475ab632e5da0f57e7c68a44fca15f722f31124fbb9e8af512f46aa2ac
7
- data.tar.gz: d777cac77ceeb2a98c8f37a8e001d9240403b47a1e2326a6f1bc42c3cf48e7813dd69ab2260ce5a29a34b7b9b3e0ef54fa541785f8b5dbc82b787381e549525e
6
+ metadata.gz: 59171eef265ace4aab9295b9e3866233138194afc0ee691022d5c6ec9f4ca6b53400a01109f9a745cdf54d70cd8c74a1fd39ff319624bf3f3ff04d2ae409ca26
7
+ data.tar.gz: a886b191dcab85164e2f7e47d2b9445121254a8b94248ce1f6e54fff31b8e0a13afc6a8dc5013f04cfaac2340957344ba03b3441d09b0018e46bb7a564af4b03
data/.rubocop.yml CHANGED
@@ -8,3 +8,5 @@ Metrics/BlockLength:
8
8
  - "*.gemspec"
9
9
  Metrics/ClassLength:
10
10
  Enabled: false
11
+ Metrics/MethodLength:
12
+ Max: 20
@@ -5,32 +5,34 @@ module Mihari
5
5
  class Base
6
6
  extend Dry::Initializer
7
7
 
8
- option :rule, default: proc {}
9
-
10
8
  include Mixins::Configurable
11
9
  include Mixins::Retriable
12
10
 
13
- # @return [Mihari::Structs::Rule, nil]
14
- attr_reader :rule
15
-
16
- def initialize(*args, **kwargs)
17
- super(*args, **kwargs)
18
-
19
- @base_time = Time.now.utc
11
+ # @return [Array<String>, Array<Mihari::Artifact>]
12
+ def artifacts
13
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
20
14
  end
21
15
 
22
16
  #
23
- # Load/overwrite rule
17
+ # Normalize artifacts
18
+ # - Convert data (string) into an artifact
19
+ # - Reject an invalid artifact
24
20
  #
25
- # @param [String] path_or_id
21
+ # @return [Array<Mihari::Artifact>]
26
22
  #
27
- def load_rule(path_or_id)
28
- @rule = Structs::Rule.from_path_or_id path_or_id
29
- end
30
-
31
- # @return [Array<String>, Array<Mihari::Artifact>]
32
- def artifacts
33
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
23
+ def normalized_artifacts
24
+ retry_on_error do
25
+ @normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
26
+ # No need to set data_type manually
27
+ # It is set automatically in #initialize
28
+ artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
29
+ artifact
30
+ end.select(&:valid?).uniq(&:data).map do |artifact|
31
+ # set source
32
+ artifact.source = source
33
+ artifact
34
+ end
35
+ end
34
36
  end
35
37
 
36
38
  # @return [String]
@@ -43,109 +45,12 @@ module Mihari
43
45
  self.class.to_s.split("::").last
44
46
  end
45
47
 
46
- #
47
- # Set artifacts & run emitters in parallel
48
- #
49
- # @return [Mihari::Alert, nil]
50
- #
51
- def run
52
- raise ConfigurationError, "#{class_name} is not configured correctly" unless configured?
53
-
54
- alert_or_something = bulk_emit
55
- # returns Mihari::Alert created by the database emitter
56
- alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
57
- end
58
-
59
- #
60
- # Bulk emit
61
- #
62
- # @return [Array<Mihari::Alert>]
63
- #
64
- def bulk_emit
65
- Parallel.map(valid_emitters) { |emitter| emit emitter }.compact
66
- end
67
-
68
- #
69
- # Emit an alert
70
- #
71
- # @param [Mihari::Emitters::Base] emitter
72
- #
73
- # @return [Mihari::Alert, nil]
74
- #
75
- def emit(emitter)
76
- return if enriched_artifacts.empty?
77
-
78
- alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
79
-
80
- Mihari.logger.info "Emission by #{emitter.class} is succedded"
81
-
82
- alert_or_something
83
- rescue StandardError => e
84
- Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
85
- end
86
-
87
48
  class << self
88
49
  def inherited(child)
89
50
  super
90
51
  Mihari.analyzers << child
91
52
  end
92
53
  end
93
-
94
- #
95
- # Normalize artifacts
96
- # - Convert data (string) into an artifact
97
- # - Set rule ID
98
- # - Reject an invalid artifact
99
- # - Uniquefy artifacts by data
100
- #
101
- # @return [Array<Mihari::Artifact>]
102
- #
103
- def normalized_artifacts
104
- @normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
105
- # No need to set data_type manually
106
- # It is set automatically in #initialize
107
- artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
108
- artifact.rule_id = rule&.id
109
- artifact
110
- end.select(&:valid?).uniq(&:data)
111
- end
112
-
113
- private
114
-
115
- #
116
- # Uniquefy artifacts (assure rule level uniqueness)
117
- #
118
- # @return [Array<Mihari::Artifact>]
119
- #
120
- def unique_artifacts
121
- @unique_artifacts ||= normalized_artifacts.select do |artifact|
122
- artifact.unique?(base_time: @base_time, artifact_lifetime: rule&.artifact_lifetime)
123
- end
124
- end
125
-
126
- #
127
- # Enriched artifacts
128
- #
129
- # @return [Array<Mihari::Artifact>]
130
- #
131
- def enriched_artifacts
132
- @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
133
- artifact.enrich_all
134
- artifact
135
- end
136
- end
137
-
138
- #
139
- # Select valid emitters
140
- #
141
- # @return [Array<Mihari::Emitters::Base>]
142
- #
143
- def valid_emitters
144
- @valid_emitters ||= Mihari.emitters.filter_map do |klass|
145
- emitter = klass.new
146
- emitter.valid? ? emitter : nil
147
- end.compact
148
- end
149
54
  end
150
55
  end
151
56
  end
@@ -26,15 +26,18 @@ module Mihari
26
26
  @secret = kwargs[:secret] || Mihari.config.censys_secret
27
27
  end
28
28
 
29
+ #
30
+ # @return [Array<Mihari::Artifact>]
31
+ #
29
32
  def artifacts
30
33
  artifacts = []
31
34
 
32
35
  cursor = nil
33
36
  loop do
34
37
  response = client.search(query, cursor: cursor)
35
- artifacts << response.result.to_artifacts(source)
38
+ artifacts << response.result.to_artifacts
36
39
  cursor = response.result.links.next
37
- break if cursor == ""
40
+ break if cursor.nil?
38
41
 
39
42
  # sleep #{interval} seconds to avoid the rate limitation (if it is set)
40
43
  sleep interval
@@ -43,24 +46,39 @@ module Mihari
43
46
  artifacts.flatten.uniq(&:data)
44
47
  end
45
48
 
49
+ #
50
+ # @return [Boolean]
51
+ #
46
52
  def configured?
47
53
  configuration_keys.all? { |key| Mihari.config.send(key) } || (id? && secret?)
48
54
  end
49
55
 
50
56
  private
51
57
 
58
+ #
59
+ # @return [Array<String>]
60
+ #
52
61
  def configuration_keys
53
62
  %w[censys_id censys_secret]
54
63
  end
55
64
 
65
+ #
66
+ # @return [Mihari::Clients::Censys]
67
+ #
56
68
  def client
57
69
  @client ||= Clients::Censys.new(id: id, secret: secret)
58
70
  end
59
71
 
72
+ #
73
+ # @return [Boolean]
74
+ #
60
75
  def id?
61
76
  !id.nil?
62
77
  end
63
78
 
79
+ #
80
+ # @return [Boolean]
81
+ #
64
82
  def secret?
65
83
  !secret.nil?
66
84
  end
@@ -28,7 +28,7 @@ module Mihari
28
28
  responses = search
29
29
  return [] unless responses
30
30
 
31
- responses.map { |response| response.to_artifacts(source) }.flatten
31
+ responses.map(&:to_artifacts).flatten
32
32
  end
33
33
 
34
34
  private
@@ -34,14 +34,21 @@ module Mihari
34
34
  "webhook" => Emitters::Webhook
35
35
  }.freeze
36
36
 
37
- # @return [Mihari::Structs::Rule]
38
- attr_reader :rule
39
-
40
- class Rule < Base
37
+ class Rule
41
38
  include Mixins::FalsePositive
42
39
 
43
- def initialize(**kwargs)
44
- super(**kwargs)
40
+ # @return [Mihari::Structs::Rule]
41
+ attr_reader :rule
42
+
43
+ # @return [Time]
44
+ attr_reader :base_time
45
+
46
+ #
47
+ # @param [Mihari::Structs::Rule] rule
48
+ #
49
+ def initialize(rule:)
50
+ @rule = rule
51
+ @base_time = Time.now.utc
45
52
 
46
53
  validate_analyzer_configurations
47
54
  end
@@ -52,41 +59,15 @@ module Mihari
52
59
  # @return [Array<Mihari::Artifact>]
53
60
  #
54
61
  def artifacts
55
- artifacts = []
56
-
57
- rule.queries.each do |original_params|
58
- parmas = original_params.deep_dup
59
-
60
- analyzer_name = parmas[:analyzer]
61
- klass = get_analyzer_class(analyzer_name)
62
-
63
- query = parmas[:query]
64
-
65
- # set interval in the top level
66
- options = parmas[:options] || {}
67
- interval = options[:interval]
68
-
69
- parmas[:interval] = interval if interval
70
-
71
- # set rule
72
- parmas[:rule] = rule
73
-
74
- analyzer = klass.new(query, **parmas)
75
-
76
- # Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
77
- # So Mihari::Artifact object has "source" attribute (e.g. "Shodan")
78
- artifacts << analyzer.normalized_artifacts
79
- end
80
-
81
- artifacts.flatten
62
+ rule.queries.map { |params| run_query(params.deep_dup) }.flatten
82
63
  end
83
64
 
84
65
  #
85
66
  # Normalize artifacts
86
- # - Uniquefy artifacts by #uniq(&:data)
87
- # - Reject an invalid artifact (for just in case)
67
+ # - Reject invalid artifacts (for just in case)
88
68
  # - Select artifacts with allowed data types
89
- # - Reject artifacts with disallowed data values
69
+ # - Reject artifacts with false positive values
70
+ # - Set rule ID
90
71
  #
91
72
  # @return [Array<Mihari::Artifact>]
92
73
  #
@@ -95,6 +76,20 @@ module Mihari
95
76
  rule.data_types.include? artifact.data_type
96
77
  end.reject do |artifact|
97
78
  falsepositive? artifact.data
79
+ end.map do |artifact|
80
+ artifact.rule_id = rule.id
81
+ artifact
82
+ end
83
+ end
84
+
85
+ #
86
+ # Uniquify artifacts (assure rule level uniqueness)
87
+ #
88
+ # @return [Array<Mihari::Artifact>]
89
+ #
90
+ def unique_artifacts
91
+ @unique_artifacts ||= normalized_artifacts.select do |artifact|
92
+ artifact.unique?(base_time: base_time, artifact_lifetime: rule.artifact_lifetime)
98
93
  end
99
94
  end
100
95
 
@@ -105,39 +100,93 @@ module Mihari
105
100
  #
106
101
  def enriched_artifacts
107
102
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
108
- rule.enrichers.each do |enricher|
109
- artifact.enrich_by_enricher(enricher[:enricher])
110
- end
111
-
103
+ rule.enrichers.each { |enricher| artifact.enrich_by_enricher enricher[:enricher] }
112
104
  artifact
113
105
  end
114
106
  end
115
107
 
116
108
  #
117
- # Normalized disallowed data values
109
+ # Bulk emit
110
+ #
111
+ # @return [Array<Mihari::Alert>]
112
+ #
113
+ def bulk_emit
114
+ Parallel.map(valid_emitters) { |emitter| emit emitter }.compact
115
+ end
116
+
117
+ #
118
+ # Emit an alert
119
+ #
120
+ # @param [Mihari::Emitters::Base] emitter
118
121
  #
119
- # @return [Array<Regexp, String>]
122
+ # @return [Mihari::Alert, nil]
120
123
  #
121
- def normalized_falsepositives
122
- @normalized_falsepositives ||= rule.falsepositives.map { |v| normalize_falsepositive v }
124
+ def emit(emitter)
125
+ return if enriched_artifacts.empty?
126
+
127
+ alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
128
+
129
+ Mihari.logger.info "Emission by #{emitter.class} is succeeded"
130
+
131
+ alert_or_something
132
+ rescue StandardError => e
133
+ Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
123
134
  end
124
135
 
136
+ #
137
+ # Set artifacts & run emitters in parallel
138
+ #
139
+ # @return [Mihari::Alert, nil]
140
+ #
141
+ def run
142
+ # memoize enriched artifacts
143
+ enriched_artifacts
144
+
145
+ alert_or_something = bulk_emit
146
+ # returns Mihari::Alert created by the database emitter
147
+ alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
148
+ end
149
+
150
+ private
151
+
125
152
  #
126
153
  # Check whether a value is a falsepositive value or not
127
154
  #
128
155
  # @return [Boolean]
129
156
  #
130
157
  def falsepositive?(value)
131
- return true if normalized_falsepositives.include?(value)
158
+ return true if rule.falsepositives.include?(value)
132
159
 
133
- normalized_falsepositives.select do |falsepositive|
160
+ rule.falsepositives.select do |falsepositive|
134
161
  falsepositive.is_a?(Regexp)
135
162
  end.any? do |falseposistive|
136
163
  falseposistive.match?(value)
137
164
  end
138
165
  end
139
166
 
140
- private
167
+ #
168
+ # @param [Hash] params
169
+ #
170
+ # @return [Array<Mihari::Artifact>]
171
+ #
172
+ def run_query(params)
173
+ analyzer_name = params[:analyzer]
174
+ klass = get_analyzer_class(analyzer_name)
175
+
176
+ # set interval in the top level
177
+ options = params[:options] || {}
178
+ interval = options[:interval]
179
+ params[:interval] = interval if interval
180
+
181
+ # set rule
182
+ params[:rule] = rule
183
+ query = params[:query]
184
+ analyzer = klass.new(query, **params)
185
+
186
+ # Use #normalized_artifacts method to get artifacts as Array<Mihari::Artifact>
187
+ # So Mihari::Artifact object has "source" attribute (e.g. "Shodan")
188
+ analyzer.normalized_artifacts
189
+ end
141
190
 
142
191
  #
143
192
  # Get emitter class
@@ -153,18 +202,26 @@ module Mihari
153
202
  raise ArgumentError, "#{emitter_name} is not supported"
154
203
  end
155
204
 
156
- def valid_emitters
157
- @valid_emitters ||= rule.emitters.filter_map do |original_params|
158
- params = original_params.deep_dup
205
+ #
206
+ # @param [Hash] params
207
+ #
208
+ # @return [Mihari::Emitter:Base]
209
+ #
210
+ def validate_emitter(params)
211
+ name = params[:emitter]
212
+ params.delete(:emitter)
159
213
 
160
- name = params[:emitter]
161
- params.delete(:emitter)
214
+ klass = get_emitter_class(name)
215
+ emitter = klass.new(**params)
162
216
 
163
- klass = get_emitter_class(name)
164
- emitter = klass.new(**params)
217
+ emitter.valid? ? emitter : nil
218
+ end
165
219
 
166
- emitter.valid? ? emitter : nil
167
- end
220
+ #
221
+ # @return [Array<Mihari::Emitter::Base>]
222
+ #
223
+ def valid_emitters
224
+ @valid_emitters ||= rule.emitters.filter_map { |params| validate_emitter(params.deep_dup) }
168
225
  end
169
226
 
170
227
  #
@@ -187,13 +244,12 @@ module Mihari
187
244
  def validate_analyzer_configurations
188
245
  rule.queries.each do |params|
189
246
  analyzer_name = params[:analyzer]
247
+
190
248
  klass = get_analyzer_class(analyzer_name)
249
+ klass_name = klass.to_s.split("::").last
191
250
 
192
251
  instance = klass.new("dummy")
193
- unless instance.configured?
194
- klass_name = klass.to_s.split("::").last
195
- raise ConfigurationError, "#{klass_name} is not configured correctly"
196
- end
252
+ raise ConfigurationError, "#{klass_name} is not configured correctly" unless instance.configured?
197
253
  end
198
254
  end
199
255
  end
@@ -26,7 +26,7 @@ module Mihari
26
26
  results = search
27
27
  return [] if results.empty?
28
28
 
29
- results.map { |result| result.to_artifacts(source) }.flatten.uniq(&:data)
29
+ results.map(&:to_artifacts).flatten.uniq(&:data)
30
30
  end
31
31
 
32
32
  private
@@ -36,15 +36,12 @@ module Mihari
36
36
 
37
37
  def artifacts
38
38
  responses = search
39
- results = responses.map(&:results).flatten
40
-
41
- allowed_data_types.map do |type|
42
- results.filter_map do |result|
43
- page = result.page
44
- data = page.send(type.to_sym)
45
- data.nil? ? nil : Artifact.new(data: data, source: source, metadata: result)
46
- end
47
- end.flatten
39
+ # @type [Array<Mihari::Artifact>]
40
+ artifacts = responses.map { |res| res.to_artifacts }.flatten
41
+
42
+ artifacts.select do |artifact|
43
+ allowed_data_types.include? artifact.data_type
44
+ end
48
45
  end
49
46
 
50
47
  private
@@ -26,11 +26,7 @@ module Mihari
26
26
 
27
27
  def artifacts
28
28
  responses = search_with_cursor
29
- responses.map do |response|
30
- response.data.map do |datum|
31
- Artifact.new(data: datum.value, source: source, metadata: datum.metadata)
32
- end
33
- end.flatten
29
+ responses.map(&:to_artifacts).flatten
34
30
  end
35
31
 
36
32
  private
@@ -3,7 +3,7 @@
3
3
  require "thor"
4
4
 
5
5
  # Commands
6
- require "mihari/commands/searcher"
6
+ require "mihari/commands/search"
7
7
  require "mihari/commands/version"
8
8
  require "mihari/commands/web"
9
9
  require "mihari/commands/database"
@@ -17,7 +17,7 @@ require "mihari/cli/rule"
17
17
  module Mihari
18
18
  module CLI
19
19
  class Main < Base
20
- include Mihari::Commands::Searcher
20
+ include Mihari::Commands::Search
21
21
  include Mihari::Commands::Version
22
22
  include Mihari::Commands::Web
23
23
 
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module Search
6
+ include Mixins::ErrorNotification
7
+
8
+ def self.included(thor)
9
+ thor.class_eval do
10
+ desc "search [PATH]", "Search by a rule"
11
+ method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
12
+ #
13
+ # Search by a rule
14
+ #
15
+ # @param [String] path_or_id
16
+ #
17
+ def search(path_or_id)
18
+ Mihari::Database.with_db_connection do
19
+ rule = Structs::Rule.from_path_or_id path_or_id
20
+
21
+ begin
22
+ rule.validate!
23
+ rescue RuleValidationError
24
+ return
25
+ end
26
+
27
+ update_rule rule
28
+ run_rule rule
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # @param [Mihari::Structs::Rule] rule
35
+ #
36
+ def update_rule(rule)
37
+ force_overwrite = options["force_overwrite"] || false
38
+ begin
39
+ rule_model = Mihari::Rule.find(rule.id)
40
+ has_change = rule_model.data != rule.data.deep_stringify_keys
41
+ has_change_and_not_force_overwrite = has_change & !force_overwrite
42
+
43
+ confirm_message = "This operation will overwrite the rule in the database (Rule ID: #{rule.id}). Are you sure you want to update the rule? (y/n)"
44
+ return if has_change_and_not_force_overwrite && !yes?(confirm_message)
45
+ # update the rule
46
+ rule.model.save
47
+ rescue ActiveRecord::RecordNotFound
48
+ # create a new rule
49
+ rule.model.save
50
+ end
51
+ end
52
+
53
+ #
54
+ # @param [Mihari::Structs::Rule] rule
55
+ #
56
+ def run_rule(rule)
57
+ with_error_notification do
58
+ alert = rule.analyzer.run
59
+ if alert
60
+ data = Mihari::Entities::Alert.represent(alert)
61
+ puts JSON.pretty_generate(data.as_json)
62
+ else
63
+ Mihari.logger.info "There is no new alert created in the database"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -6,8 +6,6 @@ module Mihari
6
6
  #
7
7
  # Send an exception notification if there is any error in a block
8
8
  #
9
- # @return [Nil]
10
- #
11
9
  def with_error_notification
12
10
  yield
13
11
  rescue StandardError => e
@@ -60,7 +60,7 @@ module Mihari
60
60
  ).order(created_at: :desc).first
61
61
  return true if artifact.nil?
62
62
 
63
- # check whetehr the artifact is decayed or not
63
+ # check whether the artifact is decayed or not
64
64
  return false if artifact_lifetime.nil?
65
65
 
66
66
  # use the current UTC time if base_time is not given (for testing)
@@ -25,30 +25,15 @@ module Mihari
25
25
  AnalyzerWithoutAPIKey | AnalyzerWithAPIKey | Censys | CIRCL | PassiveTotal | ZoomEye | Urlscan | Crtsh | Feed
26
26
  end
27
27
 
28
- optional(:emitters).value(:array).each { Database | MISP | TheHive | Slack | Webhook }
28
+ optional(:emitters).value(:array).each { Database | MISP | TheHive | Slack | Webhook }.default(DEFAULT_EMITTERS)
29
29
 
30
- optional(:enrichers).value(:array).each(Enricher)
30
+ optional(:enrichers).value(:array).each(Enricher).default(DEFAULT_ENRICHERS)
31
31
 
32
32
  optional(:data_types).value(array[Types::DataTypes]).default(DEFAULT_DATA_TYPES)
33
33
  optional(:falsepositives).value(array[:string]).default([])
34
34
 
35
35
  optional(:artifact_lifetime).value(:integer)
36
36
  optional(:artifact_ttl).value(:integer)
37
-
38
- before(:key_coercer) do |result|
39
- # it looks like that dry-schema v1.9.1 has an issue with setting an array of schemas as a default value
40
- # e.g. optional(:emitters).value(:array).each { Emitter | HTTP }.default(DEFAULT_EMITTERS) does not work well
41
- # so let's do a dirty hack...
42
- h = result.to_h
43
-
44
- emitters = h[:emitters]
45
- h[:emitters] = emitters || DEFAULT_EMITTERS
46
-
47
- enrichers = h[:enrichers]
48
- h[:enrichers] = enrichers || DEFAULT_ENRICHERS
49
-
50
- h
51
- end
52
37
  end
53
38
 
54
39
  class RuleContract < Dry::Validation::Contract