mihari 5.4.9 → 5.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/docs/analyzers/binaryedge.md +2 -2
  3. data/docs/analyzers/censys.md +3 -3
  4. data/docs/analyzers/circl.md +3 -3
  5. data/docs/analyzers/crtsh.md +2 -2
  6. data/docs/analyzers/dnstwister.md +1 -1
  7. data/docs/analyzers/feed.md +7 -7
  8. data/docs/analyzers/greynoise.md +2 -2
  9. data/docs/analyzers/hunterhow.md +4 -4
  10. data/docs/analyzers/index.md +13 -8
  11. data/docs/analyzers/onyphe.md +2 -2
  12. data/docs/analyzers/otx.md +2 -2
  13. data/docs/analyzers/passivetotal.md +7 -3
  14. data/docs/analyzers/pulsedive.md +2 -2
  15. data/docs/analyzers/securitytrails.md +6 -2
  16. data/docs/analyzers/shodan.md +2 -2
  17. data/docs/analyzers/urlscan.md +2 -2
  18. data/docs/analyzers/virustotal.md +6 -2
  19. data/docs/analyzers/virustotal_intelligence.md +6 -2
  20. data/docs/analyzers/zoomeye.md +3 -3
  21. data/docs/emitters/hive.md +4 -4
  22. data/docs/emitters/index.md +29 -0
  23. data/docs/emitters/misp.md +2 -2
  24. data/docs/emitters/slack.md +2 -7
  25. data/docs/emitters/webhook.md +4 -4
  26. data/docs/enrichers/index.md +29 -0
  27. data/docs/enrichers/ipinfo.md +7 -0
  28. data/docs/index.md +0 -2
  29. data/docs/installation.md +1 -1
  30. data/docs/rule.md +12 -15
  31. data/docs/usage.md +5 -2
  32. data/frontend/package-lock.json +294 -2772
  33. data/frontend/package.json +10 -10
  34. data/frontend/src/components/ErrorMessage.vue +0 -1
  35. data/frontend/src/components/alert/Alerts.vue +0 -1
  36. data/frontend/src/components/alert/AlertsWithPagination.vue +0 -1
  37. data/frontend/src/components/alert/AlertsWrapper.vue +0 -6
  38. data/frontend/src/components/alert/Form.vue +1 -3
  39. data/frontend/src/components/artifact/Artifact.vue +0 -17
  40. data/frontend/src/components/artifact/ArtifactWrapper.vue +0 -2
  41. data/frontend/src/components/artifact/WhoisRecord.vue +0 -3
  42. data/frontend/src/components/config/ConfigsWrapper.vue +0 -2
  43. data/frontend/src/components/rule/EditRule.vue +0 -3
  44. data/frontend/src/components/rule/EditRuleWrapper.vue +0 -2
  45. data/frontend/src/components/rule/Form.vue +1 -3
  46. data/frontend/src/components/rule/NewRule.vue +0 -3
  47. data/frontend/src/components/rule/Rule.vue +1 -7
  48. data/frontend/src/components/rule/RuleWrapper.vue +0 -2
  49. data/frontend/src/components/rule/RulesWrapper.vue +0 -6
  50. data/frontend/src/swagger.yaml +254 -254
  51. data/lib/mihari/analyzers/base.rb +7 -37
  52. data/lib/mihari/analyzers/binaryedge.rb +5 -1
  53. data/lib/mihari/analyzers/censys.rb +6 -1
  54. data/lib/mihari/analyzers/greynoise.rb +5 -1
  55. data/lib/mihari/analyzers/hunterhow.rb +5 -1
  56. data/lib/mihari/analyzers/onyphe.rb +5 -1
  57. data/lib/mihari/analyzers/passivetotal.rb +9 -0
  58. data/lib/mihari/analyzers/pulsedive.rb +1 -1
  59. data/lib/mihari/analyzers/rule.rb +55 -54
  60. data/lib/mihari/analyzers/securitytrails.rb +9 -0
  61. data/lib/mihari/analyzers/shodan.rb +5 -1
  62. data/lib/mihari/analyzers/urlscan.rb +5 -1
  63. data/lib/mihari/analyzers/virustotal.rb +11 -2
  64. data/lib/mihari/analyzers/virustotal_intelligence.rb +21 -1
  65. data/lib/mihari/analyzers/zoomeye.rb +7 -3
  66. data/lib/mihari/base.rb +69 -0
  67. data/lib/mihari/cli/main.rb +36 -0
  68. data/lib/mihari/clients/base.rb +7 -7
  69. data/lib/mihari/clients/binaryedge.rb +10 -4
  70. data/lib/mihari/clients/censys.rb +11 -4
  71. data/lib/mihari/clients/greynoise.rb +10 -4
  72. data/lib/mihari/clients/hunterhow.rb +10 -4
  73. data/lib/mihari/clients/misp.rb +3 -2
  74. data/lib/mihari/clients/onyphe.rb +10 -4
  75. data/lib/mihari/clients/shodan.rb +10 -4
  76. data/lib/mihari/clients/the_hive.rb +3 -2
  77. data/lib/mihari/clients/urlscan.rb +9 -3
  78. data/lib/mihari/clients/virustotal.rb +10 -4
  79. data/lib/mihari/clients/zoomeye.rb +11 -5
  80. data/lib/mihari/commands/alert.rb +6 -33
  81. data/lib/mihari/commands/rule.rb +7 -12
  82. data/lib/mihari/commands/search.rb +10 -38
  83. data/lib/mihari/config.rb +8 -0
  84. data/lib/mihari/constants.rb +3 -3
  85. data/lib/mihari/emitters/base.rb +22 -15
  86. data/lib/mihari/emitters/database.rb +1 -1
  87. data/lib/mihari/emitters/misp.rb +7 -6
  88. data/lib/mihari/emitters/slack.rb +24 -6
  89. data/lib/mihari/emitters/the_hive.rb +8 -7
  90. data/lib/mihari/emitters/webhook.rb +31 -29
  91. data/lib/mihari/enrichers/base.rb +25 -19
  92. data/lib/mihari/enrichers/google_public_dns.rb +38 -38
  93. data/lib/mihari/enrichers/ipinfo.rb +32 -34
  94. data/lib/mihari/enrichers/shodan.rb +18 -26
  95. data/lib/mihari/enrichers/whois.rb +121 -111
  96. data/lib/mihari/mixins/retriable.rb +4 -2
  97. data/lib/mihari/models/artifact.rb +37 -23
  98. data/lib/mihari/models/autonomous_system.rb +3 -2
  99. data/lib/mihari/models/cpe.rb +3 -2
  100. data/lib/mihari/models/dns.rb +3 -2
  101. data/lib/mihari/models/geolocation.rb +3 -2
  102. data/lib/mihari/models/port.rb +3 -2
  103. data/lib/mihari/models/reverse_dns.rb +3 -2
  104. data/lib/mihari/models/whois.rb +4 -3
  105. data/lib/mihari/schemas/analyzer.rb +24 -23
  106. data/lib/mihari/schemas/emitter.rb +32 -25
  107. data/lib/mihari/schemas/enricher.rb +21 -2
  108. data/lib/mihari/schemas/options.rb +27 -0
  109. data/lib/mihari/schemas/rule.rb +8 -4
  110. data/lib/mihari/services/alert_runner.rb +1 -1
  111. data/lib/mihari/services/rule_runner.rb +1 -11
  112. data/lib/mihari/types.rb +1 -14
  113. data/lib/mihari/version.rb +1 -1
  114. data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
  115. data/lib/mihari/web/public/assets/{index-33165282.css → index-56fc2187.css} +1 -1
  116. data/lib/mihari/web/public/assets/index-9cc489e6.js +1749 -0
  117. data/lib/mihari/web/public/index.html +2 -2
  118. data/lib/mihari/web/public/redoc-static.html +400 -400
  119. data/lib/mihari.rb +67 -37
  120. data/mihari.gemspec +3 -2
  121. data/mkdocs.yml +8 -6
  122. data/requirements.txt +1 -1
  123. metadata +24 -8
  124. data/lib/mihari/web/public/assets/index-a92abd57.js +0 -1740
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Mihari
4
4
  module Analyzers
5
- class Base
5
+ class Base < Mihari::Base
6
6
  include Dry::Monads[:result, :try]
7
7
 
8
8
  include Mixins::Configurable
@@ -11,37 +11,21 @@ module Mihari
11
11
  # @return [String]
12
12
  attr_reader :query
13
13
 
14
- # @return [Hash]
15
- attr_reader :options
16
-
17
14
  #
18
15
  # @param [String] query
19
16
  # @param [Hash, nil] options
20
17
  #
21
18
  def initialize(query, options: nil)
22
- @query = query
23
- @options = options || {}
24
- end
25
-
26
- #
27
- # @return [Integer, nil]
28
- #
29
- def interval
30
- options[:interval]
31
- end
19
+ super(options: options)
32
20
 
33
- #
34
- # @return [Integer]
35
- #
36
- def retry_interval
37
- options[:retry_interval] || Mihari.config.retry_interval
21
+ @query = query
38
22
  end
39
23
 
40
24
  #
41
25
  # @return [Integer]
42
26
  #
43
- def retry_times
44
- options[:retry_times] || Mihari.config.retry_times
27
+ def pagination_interval
28
+ options[:pagination_interval] || Mihari.config.pagination_interval
45
29
  end
46
30
 
47
31
  #
@@ -61,13 +45,6 @@ module Mihari
61
45
  Mihari.config.ignore_error
62
46
  end
63
47
 
64
- #
65
- # @return [Integer, nil]
66
- #
67
- def timeout
68
- options[:timeout]
69
- end
70
-
71
48
  # @return [Array<String>, Array<Mihari::Artifact>]
72
49
  def artifacts
73
50
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
@@ -81,12 +58,12 @@ module Mihari
81
58
  # @return [Array<Mihari::Artifact>]
82
59
  #
83
60
  def normalized_artifacts
84
- retry_on_error(times: retry_times, interval: retry_interval) do
61
+ retry_on_error(times: retry_times, interval: retry_interval, exponential_backoff: retry_exponential_backoff) do
85
62
  artifacts.compact.sort.map do |artifact|
86
63
  # No need to set data_type manually
87
64
  # It is set automatically in #initialize
88
65
  artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
89
- artifact.source = source
66
+ artifact.source = self.class.class_key
90
67
  artifact
91
68
  end.select(&:valid?).uniq(&:data)
92
69
  end
@@ -99,13 +76,6 @@ module Mihari
99
76
  Try[StandardError] { normalized_artifacts }.to_result
100
77
  end
101
78
 
102
- # @return [String]
103
- def class_name
104
- self.class.to_s.split("::").last
105
- end
106
-
107
- alias source class_name
108
-
109
79
  class << self
110
80
  #
111
81
  # Initialize an analyzer by query params
@@ -32,7 +32,11 @@ module Mihari
32
32
  # @return [Mihari::Clients::BinaryEdge]
33
33
  #
34
34
  def client
35
- @client ||= Clients::BinaryEdge.new(api_key: api_key, interval: interval, timeout: timeout)
35
+ @client ||= Clients::BinaryEdge.new(
36
+ api_key: api_key,
37
+ pagination_interval: pagination_interval,
38
+ timeout: timeout
39
+ )
36
40
  end
37
41
  end
38
42
  end
@@ -52,7 +52,12 @@ module Mihari
52
52
  # @return [Mihari::Clients::Censys]
53
53
  #
54
54
  def client
55
- @client ||= Clients::Censys.new(id: id, secret: secret, interval: interval, timeout: timeout)
55
+ @client ||= Clients::Censys.new(
56
+ id: id,
57
+ secret: secret,
58
+ pagination_interval: pagination_interval,
59
+ timeout: timeout
60
+ )
56
61
  end
57
62
 
58
63
  #
@@ -31,7 +31,11 @@ module Mihari
31
31
  private
32
32
 
33
33
  def client
34
- @client ||= Clients::GreyNoise.new(api_key: api_key, interval: interval, timeout: timeout)
34
+ @client ||= Clients::GreyNoise.new(
35
+ api_key: api_key,
36
+ pagination_interval: pagination_interval,
37
+ timeout: timeout
38
+ )
35
39
  end
36
40
  end
37
41
  end
@@ -46,7 +46,11 @@ module Mihari
46
46
  private
47
47
 
48
48
  def client
49
- @client ||= Clients::HunterHow.new(api_key: api_key, interval: interval, timeout: timeout)
49
+ @client ||= Clients::HunterHow.new(
50
+ api_key: api_key,
51
+ pagination_interval: pagination_interval,
52
+ timeout: timeout
53
+ )
50
54
  end
51
55
  end
52
56
  end
@@ -33,7 +33,11 @@ module Mihari
33
33
  private
34
34
 
35
35
  def client
36
- @client ||= Clients::Onyphe.new(api_key: api_key, interval: interval, timeout: timeout)
36
+ @client ||= Clients::Onyphe.new(
37
+ api_key: api_key,
38
+ pagination_interval: pagination_interval,
39
+ timeout: timeout
40
+ )
37
41
  end
38
42
  end
39
43
  end
@@ -50,6 +50,15 @@ module Mihari
50
50
  %w[passivetotal_username passivetotal_api_key]
51
51
  end
52
52
 
53
+ class << self
54
+ #
55
+ # @return [Array<String>, nil]
56
+ #
57
+ def key_aliases
58
+ ["pt"]
59
+ end
60
+ end
61
+
53
62
  private
54
63
 
55
64
  def client
@@ -35,7 +35,7 @@ module Mihari
35
35
  nil
36
36
  else
37
37
  data = property["value"]
38
- Artifact.new(data: data, source: source, metadata: property)
38
+ Artifact.new(data: data, metadata: property)
39
39
  end
40
40
  end
41
41
  end
@@ -2,50 +2,17 @@
2
2
 
3
3
  module Mihari
4
4
  module Analyzers
5
- ANALYZER_TO_CLASS = {
6
- "binaryedge" => BinaryEdge,
7
- "censys" => Censys,
8
- "circl" => CIRCL,
9
- "crtsh" => Crtsh,
10
- "dnstwister" => DNSTwister,
11
- "feed" => Feed,
12
- "greynoise" => GreyNoise,
13
- "hunterhow" => HunterHow,
14
- "onyphe" => Onyphe,
15
- "otx" => OTX,
16
- "passivetotal" => PassiveTotal,
17
- "pt" => PassiveTotal,
18
- "pulsedive" => Pulsedive,
19
- "securitytrails" => SecurityTrails,
20
- "shodan" => Shodan,
21
- "st" => SecurityTrails,
22
- "urlscan" => Urlscan,
23
- "virustotal_intelligence" => VirusTotalIntelligence,
24
- "virustotal" => VirusTotal,
25
- "vt_intel" => VirusTotalIntelligence,
26
- "vt" => VirusTotal,
27
- "zoomeye" => ZoomEye
28
- }.freeze
29
-
30
- EMITTER_TO_CLASS = {
31
- "database" => Emitters::Database,
32
- "misp" => Emitters::MISP,
33
- "slack" => Emitters::Slack,
34
- "the_hive" => Emitters::TheHive,
35
- "webhook" => Emitters::Webhook
36
- }.freeze
37
-
38
5
  class Rule
39
6
  include Mixins::FalsePositive
40
7
 
41
- # @return [Mihari::Services::Rule]
8
+ # @return [Mihari::Services::RuleProxy]
42
9
  attr_reader :rule
43
10
 
44
11
  # @return [Time]
45
12
  attr_reader :base_time
46
13
 
47
14
  #
48
- # @param [Mihari::Services::Rule] rule
15
+ # @param [Mihari::Services::RuleProxy] rule
49
16
  #
50
17
  def initialize(rule)
51
18
  @rule = rule
@@ -106,7 +73,7 @@ module Mihari
106
73
  #
107
74
  def enriched_artifacts
108
75
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
109
- rule.enrichers.each { |enricher| artifact.enrich_by_enricher enricher[:enricher] }
76
+ enrichers.each { |enricher| artifact.enrich_by_enricher enricher }
110
77
  artifact
111
78
  end
112
79
  end
@@ -119,8 +86,14 @@ module Mihari
119
86
  def bulk_emit
120
87
  return [] if enriched_artifacts.empty?
121
88
 
122
- Parallel.map(valid_emitters) do |emitter|
123
- result = emitter.result
89
+ # NOTE: separate parallel execution and logging
90
+ # because the logger does not work along with Parallel
91
+ results = Parallel.map(valid_emitters) do |emitter|
92
+ emitter.result
93
+ end
94
+
95
+ results.zip(valid_emitters).map do |result_and_emitter|
96
+ result, emitter = result_and_emitter
124
97
 
125
98
  Mihari.logger.info "Emission by #{emitter.class} is failed: #{result.failure}" if result.failure?
126
99
  Mihari.logger.info "Emission by #{emitter.class} is succeeded" if result.success?
@@ -157,15 +130,14 @@ module Mihari
157
130
  #
158
131
  # Get analyzer class
159
132
  #
160
- # @param [String] analyzer_name
133
+ # @param [String] key
161
134
  #
162
135
  # @return [Class<Mihari::Analyzers::Base>] analyzer class
163
136
  #
164
- def get_analyzer_class(analyzer_name)
165
- analyzer = ANALYZER_TO_CLASS[analyzer_name]
166
- return analyzer if analyzer
137
+ def get_analyzer_class(key)
138
+ raise ArgumentError, "#{key} is not supported" unless Mihari.analyzer_to_class.key?(key)
167
139
 
168
- raise ArgumentError, "#{analyzer_name} is not supported"
140
+ Mihari.analyzer_to_class[key]
169
141
  end
170
142
 
171
143
  #
@@ -182,29 +154,28 @@ module Mihari
182
154
  #
183
155
  # Get emitter class
184
156
  #
185
- # @param [String] emitter_name
157
+ # @param [String] key
186
158
  #
187
159
  # @return [Class<Mihari::Emitters::Base>] emitter class
188
160
  #
189
- def get_emitter_class(emitter_name)
190
- emitter = EMITTER_TO_CLASS[emitter_name]
191
- return emitter if emitter
161
+ def get_emitter_class(key)
162
+ raise ArgumentError, "#{key} is not supported" unless Mihari.emitter_to_class.key?(key)
192
163
 
193
- raise ArgumentError, "#{emitter_name} is not supported"
164
+ Mihari.emitter_to_class[key]
194
165
  end
195
166
 
196
- #
197
- # Deep copied emitters
198
167
  #
199
168
  # @return [Array<Mihari::Emitters::Base>]
200
169
  #
201
170
  def emitters
202
171
  rule.emitters.map(&:deep_dup).map do |params|
203
172
  name = params[:emitter]
204
- params.delete(:emitter)
173
+ options = params[:options]
174
+
175
+ %i[emitter options].each { |key| params.delete key }
205
176
 
206
177
  klass = get_emitter_class(name)
207
- klass.new(artifacts: enriched_artifacts, rule: rule, **params)
178
+ klass.new(artifacts: enriched_artifacts, rule: rule, options: options, **params)
208
179
  end
209
180
  end
210
181
 
@@ -212,7 +183,35 @@ module Mihari
212
183
  # @return [Array<Mihari::Emitters::Base>]
213
184
  #
214
185
  def valid_emitters
215
- emitters.select(&:valid?)
186
+ @valid_emitters ||= emitters.select(&:valid?)
187
+ end
188
+
189
+ #
190
+ # Get enricher class
191
+ #
192
+ # @param [String] key
193
+ #
194
+ # @return [Class<Mihari::Enrichers::Base>] enricher class
195
+ #
196
+ def get_enricher_class(key)
197
+ raise ArgumentError, "#{key} is not supported" unless Mihari.enricher_to_class.key?(key)
198
+
199
+ Mihari.enricher_to_class[key]
200
+ end
201
+
202
+ #
203
+ # @return [Array<Mihari::Enrichers::Base>] enrichers
204
+ #
205
+ def enrichers
206
+ @enrichers ||= rule.enrichers.map(&:deep_dup).map do |params|
207
+ name = params[:enricher]
208
+ options = params[:options]
209
+
210
+ %i[enricher options].each { |key| params.delete key }
211
+
212
+ klass = get_enricher_class(name)
213
+ klass.new(options: options, **params)
214
+ end
216
215
  end
217
216
 
218
217
  #
@@ -222,7 +221,9 @@ module Mihari
222
221
  analyzers.map do |analyzer|
223
222
  next if analyzer.configured?
224
223
 
225
- message = "#{analyzer.source} is not configured correctly. #{analyzer.configuration_keys.join(", ")} is/are missing."
224
+ joined = analyzer.configuration_keys.join(", ")
225
+ be = (analyzer.configuration_keys.length > 1) ? "are" : "is"
226
+ message = "#{analyzer.class.class_key} is not configured correctly. #{joined} #{be} missing."
226
227
  raise ConfigurationError, message
227
228
  end
228
229
  end
@@ -44,6 +44,15 @@ module Mihari
44
44
  %w[securitytrails_api_key]
45
45
  end
46
46
 
47
+ class << self
48
+ #
49
+ # @return [Array<String>, nil]
50
+ #
51
+ def key_aliases
52
+ ["st"]
53
+ end
54
+ end
55
+
47
56
  private
48
57
 
49
58
  def client
@@ -34,7 +34,11 @@ module Mihari
34
34
  # @return [Clients::Shodan]
35
35
  #
36
36
  def client
37
- @client ||= Clients::Shodan.new(api_key: api_key, interval: interval, timeout: timeout)
37
+ @client ||= Clients::Shodan.new(
38
+ api_key: api_key,
39
+ pagination_interval: pagination_interval,
40
+ timeout: timeout
41
+ )
38
42
  end
39
43
  end
40
44
  end
@@ -44,7 +44,11 @@ module Mihari
44
44
  private
45
45
 
46
46
  def client
47
- @client ||= Clients::UrlScan.new(api_key: api_key, interval: interval, timeout: timeout)
47
+ @client ||= Clients::UrlScan.new(
48
+ api_key: api_key,
49
+ pagination_interval: pagination_interval,
50
+ timeout: timeout
51
+ )
48
52
  end
49
53
 
50
54
  #
@@ -39,6 +39,15 @@ module Mihari
39
39
  %w[virustotal_api_key]
40
40
  end
41
41
 
42
+ class << self
43
+ #
44
+ # @return [Array<String>, nil]
45
+ #
46
+ def key_aliases
47
+ ["vt"]
48
+ end
49
+ end
50
+
42
51
  private
43
52
 
44
53
  def client
@@ -65,7 +74,7 @@ module Mihari
65
74
  data = res["data"] || []
66
75
  data.filter_map do |item|
67
76
  data = item.dig("attributes", "ip_address")
68
- data.nil? ? nil : Artifact.new(data: data, source: source, metadata: item)
77
+ data.nil? ? nil : Artifact.new(data: data, metadata: item)
69
78
  end
70
79
  end
71
80
 
@@ -80,7 +89,7 @@ module Mihari
80
89
  data = res["data"] || []
81
90
  data.filter_map do |item|
82
91
  data = item.dig("attributes", "host_name")
83
- Artifact.new(data: data, source: source, metadata: item)
92
+ Artifact.new(data: data, metadata: item)
84
93
  end.uniq
85
94
  end
86
95
  end
@@ -25,6 +25,22 @@ module Mihari
25
25
  %w[virustotal_api_key]
26
26
  end
27
27
 
28
+ class << self
29
+ #
30
+ # @return [String]
31
+ #
32
+ def class_key
33
+ "virustotal_intelligence"
34
+ end
35
+
36
+ #
37
+ # @return [Array<String>, nil]
38
+ #
39
+ def class_key_aliases
40
+ ["vt_intel"]
41
+ end
42
+ end
43
+
28
44
  private
29
45
 
30
46
  #
@@ -33,7 +49,11 @@ module Mihari
33
49
  # @return [::VirusTotal::API]
34
50
  #
35
51
  def client
36
- @client = Clients::VirusTotal.new(api_key: api_key, interval: interval, timeout: timeout)
52
+ @client = Clients::VirusTotal.new(
53
+ api_key: api_key,
54
+ pagination_interval: pagination_interval,
55
+ timeout: timeout
56
+ )
37
57
  end
38
58
  end
39
59
  end
@@ -53,7 +53,11 @@ module Mihari
53
53
  end
54
54
 
55
55
  def client
56
- @client ||= Clients::ZoomEye.new(api_key: api_key, interval: interval, timeout: timeout)
56
+ @client ||= Clients::ZoomEye.new(
57
+ api_key: api_key,
58
+ pagination_interval: pagination_interval,
59
+ timeout: timeout
60
+ )
57
61
  end
58
62
 
59
63
  #
@@ -69,9 +73,9 @@ module Mihari
69
73
  data = match["ip"]
70
74
 
71
75
  if data.is_a?(Array)
72
- data.map { |d| Artifact.new(data: d, source: source, metadata: match) }
76
+ data.map { |d| Artifact.new(data: d, metadata: match) }
73
77
  else
74
- Artifact.new(data: data, source: source, metadata: match)
78
+ Artifact.new(data: data, metadata: match)
75
79
  end
76
80
  end.flatten
77
81
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ #
5
+ # Base class for Analyzer, Emitter and Enricher
6
+ #
7
+ class Base
8
+ # @return [Hash]
9
+ attr_reader :options
10
+
11
+ #
12
+ # @param [Hash, nil] options
13
+ #
14
+ def initialize(*_args, options: nil, **_kwargs)
15
+ @options = options || {}
16
+ end
17
+
18
+ #
19
+ # @return [Integer]
20
+ #
21
+ def retry_interval
22
+ options[:retry_interval] || Mihari.config.retry_interval
23
+ end
24
+
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ def retry_exponential_backoff
29
+ options[:retry_exponential_backoff] || Mihari.config.retry_exponential_backoff
30
+ end
31
+
32
+ #
33
+ # @return [Integer]
34
+ #
35
+ def retry_times
36
+ options[:retry_times] || Mihari.config.retry_times
37
+ end
38
+
39
+ #
40
+ # @return [Integer, nil]
41
+ #
42
+ def timeout
43
+ options[:timeout]
44
+ end
45
+
46
+ class << self
47
+ #
48
+ # @return [String]
49
+ #
50
+ def class_key
51
+ to_s.split("::").last
52
+ end
53
+
54
+ #
55
+ # @return [Array<String>, nil]
56
+ #
57
+ def class_key_aliases
58
+ nil
59
+ end
60
+
61
+ #
62
+ # @return [Array<String>]
63
+ #
64
+ def class_keys
65
+ ([class_key] + [class_key_aliases]).flatten.compact
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require "thor/hollaback"
4
5
 
5
6
  # Commands
6
7
  require "mihari/commands/alert"
@@ -19,10 +20,45 @@ require "mihari/cli/rule"
19
20
  module Mihari
20
21
  module CLI
21
22
  class Main < Base
23
+ class_option :debug, desc: "Sets up debug mode", aliases: ["-d"], type: :boolean
24
+ class_around :safe_execute
25
+
22
26
  include Mihari::Commands::Search
23
27
  include Mihari::Commands::Version
24
28
  include Mihari::Commands::Web
25
29
 
30
+ no_commands do
31
+ def unwrap_error(err)
32
+ return err unless err.is_a?(Dry::Monads::UnwrapError)
33
+
34
+ # NOTE: UnwrapError's receiver can be either of:
35
+ # - Dry::Monads::Try::Error
36
+ # - Dry::Monads::Result::Failure
37
+ receiver = err.receiver
38
+ return receiver.exception if receiver.is_a?(Dry::Monads::Try::Error)
39
+
40
+ receiver.failure
41
+ end
42
+
43
+ def safe_execute
44
+ yield
45
+ rescue StandardError => e
46
+ err = unwrap_error(e)
47
+
48
+ raise err if options["debug"]
49
+
50
+ case err
51
+ when ValidationError
52
+ warn JSON.pretty_generate(err.errors.to_h)
53
+ when StandardError
54
+ Sentry.capture_exception(err) if Sentry.initialized?
55
+ warn err
56
+ end
57
+
58
+ exit 1
59
+ end
60
+ end
61
+
26
62
  desc "db", "Sub commands for DB"
27
63
  subcommand "db", Database
28
64
 
@@ -9,8 +9,8 @@ module Mihari
9
9
  # @return [Hash]
10
10
  attr_reader :headers
11
11
 
12
- # @return [Integer, nil]
13
- attr_reader :interval
12
+ # @return [Integer]
13
+ attr_reader :pagination_interval
14
14
 
15
15
  # @return [Integer, nil]
16
16
  attr_reader :timeout
@@ -18,20 +18,20 @@ module Mihari
18
18
  #
19
19
  # @param [String] base_url
20
20
  # @param [Hash] headers
21
- # @param [Integer, nil] interval
21
+ # @param [Integer] interval
22
22
  # @param [Integer, nil] timeout
23
23
  #
24
- def initialize(base_url, headers: {}, interval: nil, timeout: nil)
24
+ def initialize(base_url, headers: {}, pagination_interval: 0, timeout: nil)
25
25
  @base_url = base_url
26
26
  @headers = headers || {}
27
- @interval = interval
27
+ @pagination_interval = pagination_interval
28
28
  @timeout = timeout
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def sleep_interval
34
- sleep(interval) if interval
33
+ def sleep_pagination_interval
34
+ sleep pagination_interval
35
35
  end
36
36
 
37
37
  #