mihari 5.6.1 → 5.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/README.md +1 -0
  4. data/config.ru +1 -1
  5. data/docs/analyzers/fofa.md +31 -0
  6. data/docs/analyzers/index.md +1 -0
  7. data/frontend/package-lock.json +183 -186
  8. data/frontend/package.json +10 -10
  9. data/frontend/src/components/alert/Form.vue +1 -14
  10. data/frontend/src/components/artifact/AS.vue +2 -8
  11. data/frontend/src/components/artifact/DnsRecords.vue +2 -8
  12. data/frontend/src/components/artifact/ReverseDnsNames.vue +2 -10
  13. data/frontend/src/components/artifact/WhoisRecord.vue +1 -1
  14. data/lib/mihari/{base.rb → actor.rb} +27 -3
  15. data/lib/mihari/analyzers/base.rb +16 -20
  16. data/lib/mihari/analyzers/binaryedge.rb +4 -1
  17. data/lib/mihari/analyzers/censys.rb +5 -3
  18. data/lib/mihari/analyzers/circl.rb +4 -1
  19. data/lib/mihari/analyzers/crtsh.rb +4 -1
  20. data/lib/mihari/analyzers/dnstwister.rb +4 -1
  21. data/lib/mihari/analyzers/feed.rb +3 -0
  22. data/lib/mihari/analyzers/fofa.rb +65 -0
  23. data/lib/mihari/analyzers/greynoise.rb +4 -1
  24. data/lib/mihari/analyzers/hunterhow.rb +7 -2
  25. data/lib/mihari/analyzers/onyphe.rb +4 -1
  26. data/lib/mihari/analyzers/otx.rb +4 -1
  27. data/lib/mihari/analyzers/passivetotal.rb +5 -2
  28. data/lib/mihari/analyzers/pulsedive.rb +4 -1
  29. data/lib/mihari/analyzers/securitytrails.rb +5 -2
  30. data/lib/mihari/analyzers/shodan.rb +4 -1
  31. data/lib/mihari/analyzers/urlscan.rb +5 -2
  32. data/lib/mihari/analyzers/virustotal.rb +9 -6
  33. data/lib/mihari/analyzers/virustotal_intelligence.rb +4 -1
  34. data/lib/mihari/analyzers/zoomeye.rb +8 -5
  35. data/lib/mihari/cli/alert.rb +3 -0
  36. data/lib/mihari/cli/base.rb +3 -0
  37. data/lib/mihari/cli/database.rb +3 -0
  38. data/lib/mihari/cli/main.rb +3 -0
  39. data/lib/mihari/cli/rule.rb +3 -0
  40. data/lib/mihari/clients/base.rb +3 -0
  41. data/lib/mihari/clients/binaryedge.rb +5 -2
  42. data/lib/mihari/clients/censys.rb +7 -4
  43. data/lib/mihari/clients/circl.rb +3 -0
  44. data/lib/mihari/clients/crtsh.rb +5 -2
  45. data/lib/mihari/clients/dnstwister.rb +3 -0
  46. data/lib/mihari/clients/fofa.rb +83 -0
  47. data/lib/mihari/clients/greynoise.rb +5 -2
  48. data/lib/mihari/clients/hunterhow.rb +5 -2
  49. data/lib/mihari/clients/misp.rb +3 -0
  50. data/lib/mihari/clients/onyphe.rb +5 -2
  51. data/lib/mihari/clients/otx.rb +3 -0
  52. data/lib/mihari/clients/passivetotal.rb +7 -4
  53. data/lib/mihari/clients/publsedive.rb +4 -1
  54. data/lib/mihari/clients/securitytrails.rb +6 -3
  55. data/lib/mihari/clients/shodan.rb +5 -2
  56. data/lib/mihari/clients/the_hive.rb +3 -0
  57. data/lib/mihari/clients/urlscan.rb +7 -4
  58. data/lib/mihari/clients/virustotal.rb +5 -2
  59. data/lib/mihari/clients/zoomeye.rb +3 -0
  60. data/lib/mihari/commands/alert.rb +5 -14
  61. data/lib/mihari/commands/database.rb +3 -0
  62. data/lib/mihari/commands/rule.rb +11 -11
  63. data/lib/mihari/commands/search.rb +9 -6
  64. data/lib/mihari/commands/version.rb +3 -0
  65. data/lib/mihari/commands/web.rb +4 -1
  66. data/lib/mihari/config.rb +139 -150
  67. data/lib/mihari/constants.rb +1 -1
  68. data/lib/mihari/database.rb +6 -0
  69. data/lib/mihari/emitters/base.rb +16 -25
  70. data/lib/mihari/emitters/database.rb +10 -9
  71. data/lib/mihari/emitters/misp.rb +20 -41
  72. data/lib/mihari/emitters/slack.rb +16 -13
  73. data/lib/mihari/emitters/the_hive.rb +18 -46
  74. data/lib/mihari/emitters/webhook.rb +34 -23
  75. data/lib/mihari/enrichers/base.rb +16 -15
  76. data/lib/mihari/enrichers/google_public_dns.rb +6 -5
  77. data/lib/mihari/enrichers/ipinfo.rb +10 -8
  78. data/lib/mihari/enrichers/shodan.rb +4 -6
  79. data/lib/mihari/enrichers/whois.rb +13 -10
  80. data/lib/mihari/errors.rb +6 -0
  81. data/lib/mihari/feed/parser.rb +3 -0
  82. data/lib/mihari/feed/reader.rb +3 -0
  83. data/lib/mihari/http.rb +6 -0
  84. data/lib/mihari/mixins/autonomous_system.rb +3 -0
  85. data/lib/mihari/mixins/configurable.rb +3 -0
  86. data/lib/mihari/mixins/error_notification.rb +3 -0
  87. data/lib/mihari/mixins/falsepositive.rb +3 -0
  88. data/lib/mihari/mixins/refang.rb +3 -0
  89. data/lib/mihari/mixins/retriable.rb +6 -2
  90. data/lib/mihari/models/alert.rb +78 -73
  91. data/lib/mihari/models/artifact.rb +186 -178
  92. data/lib/mihari/models/autonomous_system.rb +25 -20
  93. data/lib/mihari/models/cpe.rb +24 -19
  94. data/lib/mihari/models/dns.rb +27 -22
  95. data/lib/mihari/models/geolocation.rb +25 -20
  96. data/lib/mihari/models/port.rb +24 -19
  97. data/lib/mihari/models/reverse_dns.rb +24 -19
  98. data/lib/mihari/models/rule.rb +71 -66
  99. data/lib/mihari/models/tag.rb +8 -3
  100. data/lib/mihari/models/tagging.rb +8 -3
  101. data/lib/mihari/models/whois.rb +20 -17
  102. data/lib/mihari/rule.rb +357 -0
  103. data/lib/mihari/schemas/alert.rb +3 -0
  104. data/lib/mihari/schemas/analyzer.rb +105 -87
  105. data/lib/mihari/schemas/emitter.rb +12 -5
  106. data/lib/mihari/schemas/enricher.rb +11 -4
  107. data/lib/mihari/schemas/macros.rb +4 -0
  108. data/lib/mihari/schemas/mixins.rb +20 -0
  109. data/lib/mihari/schemas/rule.rb +6 -10
  110. data/lib/mihari/service.rb +16 -0
  111. data/lib/mihari/services/alert_builder.rb +8 -5
  112. data/lib/mihari/services/alert_proxy.rb +16 -7
  113. data/lib/mihari/services/alert_runner.rb +10 -14
  114. data/lib/mihari/services/rule_builder.rb +10 -7
  115. data/lib/mihari/services/rule_runner.rb +11 -13
  116. data/lib/mihari/structs/binaryedge.rb +14 -29
  117. data/lib/mihari/structs/censys.rb +54 -133
  118. data/lib/mihari/structs/config.rb +20 -31
  119. data/lib/mihari/structs/filters.rb +38 -0
  120. data/lib/mihari/structs/fofa.rb +44 -0
  121. data/lib/mihari/structs/google_public_dns.rb +10 -28
  122. data/lib/mihari/structs/greynoise.rb +38 -89
  123. data/lib/mihari/structs/hunterhow.rb +27 -25
  124. data/lib/mihari/structs/ipinfo.rb +14 -35
  125. data/lib/mihari/structs/onyphe.rb +36 -81
  126. data/lib/mihari/structs/shodan.rb +53 -118
  127. data/lib/mihari/structs/urlscan.rb +27 -66
  128. data/lib/mihari/structs/virustotal_intelligence.rb +23 -59
  129. data/lib/mihari/type_checker.rb +4 -0
  130. data/lib/mihari/types.rb +3 -0
  131. data/lib/mihari/version.rb +1 -1
  132. data/lib/mihari/web/api.rb +15 -10
  133. data/lib/mihari/web/app.rb +59 -54
  134. data/lib/mihari/web/endpoints/alerts.rb +94 -89
  135. data/lib/mihari/web/endpoints/artifacts.rb +115 -110
  136. data/lib/mihari/web/endpoints/configs.rb +18 -13
  137. data/lib/mihari/web/endpoints/ip_addresses.rb +21 -16
  138. data/lib/mihari/web/endpoints/rules.rb +202 -204
  139. data/lib/mihari/web/endpoints/tags.rb +41 -36
  140. data/lib/mihari/web/middleware/connection_adapter.rb +16 -9
  141. data/lib/mihari/web/middleware/error_notification_adapter.rb +17 -10
  142. data/lib/mihari/web/public/assets/{index-9cc489e6.js → index-821134e2.js} +54 -54
  143. data/lib/mihari/web/public/assets/mode-yaml-24faa242.js +8 -0
  144. data/lib/mihari/web/public/index.html +1 -1
  145. data/lib/mihari.rb +30 -13
  146. data/mihari.gemspec +9 -3
  147. data/mkdocs.yml +3 -2
  148. data/requirements.txt +1 -1
  149. metadata +44 -26
  150. data/lib/mihari/analyzers/rule.rb +0 -232
  151. data/lib/mihari/services/rule_proxy.rb +0 -182
  152. data/lib/mihari/templates/rule.yml.erb +0 -5
  153. data/lib/mihari/web/public/assets/mode-yaml-a21faa53.js +0 -8
@@ -0,0 +1,357 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ class Rule < Service
5
+ include Mixins::FalsePositive
6
+
7
+ # @return [Hash]
8
+ attr_reader :data
9
+
10
+ # @return [Array, nil]
11
+ attr_reader :errors
12
+
13
+ # @return [Time]
14
+ attr_reader :base_time
15
+
16
+ #
17
+ # Initialize
18
+ #
19
+ # @param [Hash] data
20
+ #
21
+ def initialize(**data)
22
+ super()
23
+
24
+ @data = data.deep_symbolize_keys
25
+ @errors = nil
26
+ @base_time = Time.now.utc
27
+
28
+ validate!
29
+ end
30
+
31
+ #
32
+ # @return [Boolean]
33
+ #
34
+ def errors?
35
+ return false if @errors.nil?
36
+
37
+ !@errors.empty?
38
+ end
39
+
40
+ def [](key)
41
+ data key.to_sym
42
+ end
43
+
44
+ #
45
+ # @return [String]
46
+ #
47
+ def id
48
+ data[:id]
49
+ end
50
+
51
+ #
52
+ # @return [String]
53
+ #
54
+ def title
55
+ data[:title]
56
+ end
57
+
58
+ #
59
+ # @return [String]
60
+ #
61
+ def description
62
+ data[:description]
63
+ end
64
+
65
+ #
66
+ # @return [String]
67
+ #
68
+ def yaml
69
+ data.deep_stringify_keys.to_yaml
70
+ end
71
+
72
+ #
73
+ # @return [Array<Hash>]
74
+ #
75
+ def queries
76
+ data[:queries]
77
+ end
78
+
79
+ #
80
+ # @return [Array<String>]
81
+ #
82
+ def data_types
83
+ data[:data_types]
84
+ end
85
+
86
+ #
87
+ # @return [Array<String>]
88
+ #
89
+ def tags
90
+ data[:tags]
91
+ end
92
+
93
+ #
94
+ # @return [Array<String, RegExp>]
95
+ #
96
+ def falsepositives
97
+ @falsepositives ||= data[:falsepositives].map { |fp| normalize_falsepositive fp }
98
+ end
99
+
100
+ #
101
+ # @return [Integer, nil]
102
+ #
103
+ def artifact_lifetime
104
+ data[:artifact_lifetime] || data[:artifact_ttl]
105
+ end
106
+
107
+ #
108
+ # Returns a list of artifacts matched with queries/analyzers (with the rule ID)
109
+ #
110
+ # @return [Array<Mihari::Models::Artifact>]
111
+ #
112
+ def artifacts
113
+ analyzers.flat_map do |analyzer|
114
+ # @type [Dry::Monads::Result::Success<Array<Mihari::Models::Artifact>>, Dry::Monads::Result::Failure]
115
+ result = analyzer.result
116
+
117
+ if result.failure?
118
+ raise result.failure unless analyzer.ignore_error?
119
+ else
120
+ artifacts = result.value!
121
+ artifacts.map do |artifact|
122
+ artifact.rule_id = id
123
+ artifact
124
+ end
125
+ end
126
+ end.compact
127
+ end
128
+
129
+ #
130
+ # Normalize artifacts
131
+ # - Reject invalid artifacts (for just in case)
132
+ # - Select artifacts with allowed data types
133
+ # - Reject artifacts with false positive values
134
+ # - Set rule ID
135
+ #
136
+ # @return [Array<Mihari::Models::Artifact>]
137
+ #
138
+ def normalized_artifacts
139
+ valid_artifacts = artifacts.uniq(&:data).select(&:valid?)
140
+ date_type_allowed_artifacts = valid_artifacts.select { |artifact| data_types.include? artifact.data_type }
141
+ date_type_allowed_artifacts.reject { |artifact| falsepositive? artifact.data }
142
+ end
143
+
144
+ #
145
+ # Uniquify artifacts (assure rule level uniqueness)
146
+ #
147
+ # @return [Array<Mihari::Models::Artifact>]
148
+ #
149
+ def unique_artifacts
150
+ normalized_artifacts.select do |artifact|
151
+ artifact.unique?(base_time: base_time, artifact_lifetime: artifact_lifetime)
152
+ end
153
+ end
154
+
155
+ #
156
+ # Enriched artifacts
157
+ #
158
+ # @return [Array<Mihari::Models::Artifact>]
159
+ #
160
+ def enriched_artifacts
161
+ @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
162
+ enrichers.each { |enricher| artifact.enrich_by_enricher enricher }
163
+ artifact
164
+ end
165
+ end
166
+
167
+ #
168
+ # Bulk emit
169
+ #
170
+ # @return [Array<Mihari::Models::Alert>]
171
+ #
172
+ def bulk_emit
173
+ return [] if enriched_artifacts.empty?
174
+
175
+ # NOTE: separate parallel execution and logging
176
+ # because the logger does not work along with Parallel
177
+ results = Parallel.map(emitters) { |emitter| emitter.result enriched_artifacts }
178
+ results.zip(emitters).map do |result_and_emitter|
179
+ result, emitter = result_and_emitter
180
+ Mihari.logger.info "Emission by #{emitter.class} is failed: #{result.failure}" if result.failure?
181
+ Mihari.logger.info "Emission by #{emitter.class} is succeeded" if result.success?
182
+ result.value_or nil
183
+ end.compact
184
+ end
185
+
186
+ #
187
+ # Set artifacts & run emitters in parallel
188
+ #
189
+ # @return [Mihari::Models::Alert, nil]
190
+ #
191
+ def call
192
+ # Validate analyzers & emitters before using them
193
+ analyzers
194
+ emitters
195
+
196
+ alert_or_something = bulk_emit
197
+ # Return Mihari::Models::Alert created by the database emitter
198
+ alert_or_something.find { |res| res.is_a?(Mihari::Models::Alert) }
199
+ end
200
+
201
+ #
202
+ # @return [Mihari::Models::Rule]
203
+ #
204
+ def model
205
+ rule = Mihari::Models::Rule.find(id)
206
+
207
+ rule.title = title
208
+ rule.description = description
209
+ rule.data = data
210
+
211
+ rule
212
+ rescue ActiveRecord::RecordNotFound
213
+ Mihari::Models::Rule.new(
214
+ id: id,
215
+ title: title,
216
+ description: description,
217
+ data: data
218
+ )
219
+ end
220
+
221
+ class << self
222
+ #
223
+ # Load rule from YAML string
224
+ #
225
+ # @param [String] yaml
226
+ #
227
+ # @return [Mihari::Rule]
228
+ #
229
+ def from_yaml(yaml)
230
+ data = YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
231
+ new(**data)
232
+ end
233
+
234
+ #
235
+ # @param [Mihari::Models::Rule] model
236
+ #
237
+ # @return [Mihari::Rule]
238
+ #
239
+ def from_model(model)
240
+ new(**model.data)
241
+ end
242
+ end
243
+
244
+ private
245
+
246
+ #
247
+ # Check whether a value is a falsepositive value or not
248
+ #
249
+ # @return [Boolean]
250
+ #
251
+ def falsepositive?(value)
252
+ return true if falsepositives.include?(value)
253
+
254
+ regexps = falsepositives.select { |fp| fp.is_a?(Regexp) }
255
+ regexps.any? { |fp| fp.match?(value) }
256
+ end
257
+
258
+ #
259
+ # Get analyzer class
260
+ #
261
+ # @param [String] key
262
+ #
263
+ # @return [Class<Mihari::Analyzers::Base>] analyzer class
264
+ #
265
+ def get_analyzer_class(key)
266
+ raise ArgumentError, "#{key} is not supported" unless Mihari.analyzer_to_class.key?(key)
267
+
268
+ Mihari.analyzer_to_class[key]
269
+ end
270
+
271
+ #
272
+ # @return [Array<Mihari::Analyzers::Base>]
273
+ #
274
+ def analyzers
275
+ @analyzers ||= queries.map do |query_params|
276
+ analyzer_name = query_params[:analyzer]
277
+ klass = get_analyzer_class(analyzer_name)
278
+ klass.from_query(query_params)
279
+ end.map do |analyzer|
280
+ analyzer.validate_configuration!
281
+ analyzer
282
+ end
283
+ end
284
+
285
+ #
286
+ # Get emitter class
287
+ #
288
+ # @param [String] key
289
+ #
290
+ # @return [Class<Mihari::Emitters::Base>] emitter class
291
+ #
292
+ def get_emitter_class(key)
293
+ raise ArgumentError, "#{key} is not supported" unless Mihari.emitter_to_class.key?(key)
294
+
295
+ Mihari.emitter_to_class[key]
296
+ end
297
+
298
+ #
299
+ # @return [Array<Mihari::Emitters::Base>]
300
+ #
301
+ def emitters
302
+ @emitters ||= data[:emitters].map(&:deep_dup).map do |params|
303
+ name = params[:emitter]
304
+ options = params[:options]
305
+
306
+ %i[emitter options].each { |key| params.delete key }
307
+
308
+ klass = get_emitter_class(name)
309
+ klass.new(rule: self, options: options, **params)
310
+ end.map do |emitter|
311
+ emitter.validate_configuration!
312
+ emitter
313
+ end
314
+ end
315
+
316
+ #
317
+ # Get enricher class
318
+ #
319
+ # @param [String] key
320
+ #
321
+ # @return [Class<Mihari::Enrichers::Base>] enricher class
322
+ #
323
+ def get_enricher_class(key)
324
+ raise ArgumentError, "#{key} is not supported" unless Mihari.enricher_to_class.key?(key)
325
+
326
+ Mihari.enricher_to_class[key]
327
+ end
328
+
329
+ #
330
+ # @return [Array<Mihari::Enrichers::Base>] enrichers
331
+ #
332
+ def enrichers
333
+ @enrichers ||= data[:enrichers].map(&:deep_dup).map do |params|
334
+ name = params[:enricher]
335
+ options = params[:options]
336
+
337
+ %i[enricher options].each { |key| params.delete key }
338
+
339
+ klass = get_enricher_class(name)
340
+ klass.new(options: options, **params)
341
+ end
342
+ end
343
+
344
+ #
345
+ # Validate the data format
346
+ #
347
+ def validate!
348
+ contract = Schemas::RuleContract.new
349
+ result = contract.call(data)
350
+
351
+ @data = result.to_h
352
+ @errors = result.errors
353
+
354
+ raise ValidationError.new("Validation failed", errors) if errors?
355
+ end
356
+ end
357
+ end
@@ -7,6 +7,9 @@ module Mihari
7
7
  required(:artifacts).value(array[:string])
8
8
  end
9
9
 
10
+ #
11
+ # Alert schema contract
12
+ #
10
13
  class AlertContract < Dry::Validation::Contract
11
14
  params(Alert)
12
15
  end
@@ -2,102 +2,120 @@
2
2
 
3
3
  module Mihari
4
4
  module Schemas
5
- AnalyzerAPIKeyPagination = Dry::Schema.Params do
6
- required(:analyzer).value(
7
- Types::String.enum(
8
- "binaryedge",
9
- "greynoise",
10
- "onyphe",
11
- "shodan",
12
- "urlscan",
13
- "virustotal_intelligence",
14
- "vt_intel"
15
- )
16
- )
17
- required(:query).value(:string)
18
- optional(:api_key).value(:string)
19
- optional(:options).hash(AnalyzerPaginationOptions)
20
- end
5
+ #
6
+ # Analyzer schemas
7
+ #
8
+ module Analyzers
9
+ extend Schemas::Mixins
21
10
 
22
- AnalyzerAPIKey = Dry::Schema.Params do
23
- required(:analyzer).value(
24
- Types::String.enum(
25
- "otx",
26
- "pulsedive",
27
- "securitytrails",
28
- "st",
29
- "virustotal",
30
- "vt"
31
- )
32
- )
33
- required(:query).value(:string)
34
- optional(:api_key).value(:string)
35
- optional(:options).hash(AnalyzerOptions)
36
- end
11
+ # Analyzer with API key and pagination
12
+ [
13
+ Mihari::Analyzers::BinaryEdge.class_keys,
14
+ Mihari::Analyzers::GreyNoise.class_keys,
15
+ Mihari::Analyzers::Onyphe.class_keys,
16
+ Mihari::Analyzers::Shodan.class_keys,
17
+ Mihari::Analyzers::Urlscan.class_keys,
18
+ Mihari::Analyzers::VirusTotalIntelligence.class_keys
19
+ ].each do |keys|
20
+ key = keys.first
21
+ const_set(key.upcase, Dry::Schema.Params do
22
+ required(:analyzer).value(Types::String.enum(*keys))
23
+ required(:query).value(:string)
24
+ optional(:api_key).value(:string)
25
+ optional(:options).hash(AnalyzerPaginationOptions)
26
+ end)
27
+ end
37
28
 
38
- DNSTwister = Dry::Schema.Params do
39
- required(:analyzer).value(Types::String.enum("dnstwister"))
40
- required(:query).value(:string)
41
- optional(:options).hash(AnalyzerOptions)
42
- end
29
+ # Analyzer with API key
30
+ [
31
+ Mihari::Analyzers::OTX.class_keys,
32
+ Mihari::Analyzers::Pulsedive.class_keys,
33
+ Mihari::Analyzers::VirusTotal.class_keys,
34
+ Mihari::Analyzers::SecurityTrails.class_keys
35
+ ].each do |keys|
36
+ key = keys.first
37
+ const_set(key.upcase, Dry::Schema.Params do
38
+ required(:analyzer).value(Types::String.enum(*keys))
39
+ required(:query).value(:string)
40
+ optional(:api_key).value(:string)
41
+ optional(:options).hash(AnalyzerOptions)
42
+ end)
43
+ end
43
44
 
44
- Censys = Dry::Schema.Params do
45
- required(:analyzer).value(Types::String.enum("censys"))
46
- required(:query).value(:string)
47
- optional(:id).value(:string)
48
- optional(:secret).value(:string)
49
- optional(:options).hash(AnalyzerPaginationOptions)
50
- end
45
+ DNSTwister = Dry::Schema.Params do
46
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::DNSTwister.class_keys))
47
+ required(:query).value(:string)
48
+ optional(:options).hash(AnalyzerOptions)
49
+ end
51
50
 
52
- CIRCL = Dry::Schema.Params do
53
- required(:analyzer).value(Types::String.enum("circl"))
54
- required(:query).value(:string)
55
- optional(:username).value(:string)
56
- optional(:password).value(:string)
57
- optional(:options).hash(AnalyzerOptions)
58
- end
51
+ Censys = Dry::Schema.Params do
52
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Censys.class_keys))
53
+ required(:query).value(:string)
54
+ optional(:id).value(:string)
55
+ optional(:secret).value(:string)
56
+ optional(:options).hash(AnalyzerPaginationOptions)
57
+ end
59
58
 
60
- PassiveTotal = Dry::Schema.Params do
61
- required(:analyzer).value(Types::String.enum("passivetotal", "pt"))
62
- required(:query).value(:string)
63
- optional(:username).value(:string)
64
- optional(:api_key).value(:string)
65
- optional(:options).hash(AnalyzerOptions)
66
- end
59
+ CIRCL = Dry::Schema.Params do
60
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::CIRCL.class_keys))
61
+ required(:query).value(:string)
62
+ optional(:username).value(:string)
63
+ optional(:password).value(:string)
64
+ optional(:options).hash(AnalyzerOptions)
65
+ end
67
66
 
68
- ZoomEye = Dry::Schema.Params do
69
- required(:analyzer).value(Types::String.enum("zoomeye"))
70
- required(:query).value(:string)
71
- required(:type).value(Types::String.enum("host", "web"))
72
- optional(:options).hash(AnalyzerPaginationOptions)
73
- end
67
+ Fofa = Dry::Schema.Params do
68
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Fofa.class_keys))
69
+ required(:query).value(:string)
70
+ optional(:api_key).value(:string)
71
+ optional(:email).value(:string)
72
+ optional(:options).hash(AnalyzerPaginationOptions)
73
+ end
74
74
 
75
- Crtsh = Dry::Schema.Params do
76
- required(:analyzer).value(Types::String.enum("crtsh"))
77
- required(:query).value(:string)
78
- optional(:exclude_expired).value(:bool).default(true)
79
- optional(:options).hash(AnalyzerOptions)
80
- end
75
+ PassiveTotal = Dry::Schema.Params do
76
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::PassiveTotal.class_keys))
77
+ required(:query).value(:string)
78
+ optional(:username).value(:string)
79
+ optional(:api_key).value(:string)
80
+ optional(:options).hash(AnalyzerOptions)
81
+ end
81
82
 
82
- HunterHow = Dry::Schema.Params do
83
- required(:analyzer).value(Types::String.enum("hunterhow"))
84
- required(:query).value(:string)
85
- required(:start_time).value(:date)
86
- required(:end_time).value(:date)
87
- optional(:api_key).value(:string)
88
- optional(:options).hash(AnalyzerPaginationOptions)
89
- end
83
+ ZoomEye = Dry::Schema.Params do
84
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::ZoomEye.class_keys))
85
+ required(:query).value(:string)
86
+ required(:type).value(Types::String.enum("host", "web"))
87
+ optional(:options).hash(AnalyzerPaginationOptions)
88
+ end
90
89
 
91
- Feed = Dry::Schema.Params do
92
- required(:analyzer).value(Types::String.enum("feed"))
93
- required(:query).value(:string)
94
- required(:selector).value(:string)
95
- optional(:method).value(Types::HTTPRequestMethods).default("GET")
96
- optional(:headers).value(:hash).default({})
97
- optional(:params).value(:hash)
98
- optional(:data).value(:hash)
99
- optional(:json).value(:hash)
100
- optional(:options).hash(AnalyzerOptions)
90
+ Crtsh = Dry::Schema.Params do
91
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Crtsh.class_keys))
92
+ required(:query).value(:string)
93
+ optional(:exclude_expired).value(:bool).default(true)
94
+ optional(:options).hash(AnalyzerOptions)
95
+ end
96
+
97
+ HunterHow = Dry::Schema.Params do
98
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::HunterHow.class_keys))
99
+ required(:query).value(:string)
100
+ required(:start_time).value(:date)
101
+ required(:end_time).value(:date)
102
+ optional(:api_key).value(:string)
103
+ optional(:options).hash(AnalyzerPaginationOptions)
104
+ end
105
+
106
+ Feed = Dry::Schema.Params do
107
+ required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Feed.class_keys))
108
+ required(:query).value(:string)
109
+ required(:selector).value(:string)
110
+ optional(:method).value(Types::HTTPRequestMethods).default("GET")
111
+ optional(:headers).value(:hash).default({})
112
+ optional(:params).value(:hash)
113
+ optional(:data).value(:hash)
114
+ optional(:json).value(:hash)
115
+ optional(:options).hash(AnalyzerOptions)
116
+ end
101
117
  end
118
+
119
+ Analyzer = Schemas::Analyzers.get_or_composition
102
120
  end
103
121
  end
@@ -2,21 +2,26 @@
2
2
 
3
3
  module Mihari
4
4
  module Schemas
5
+ #
6
+ # Emitter schemas
7
+ #
5
8
  module Emitters
9
+ extend Schemas::Mixins
10
+
6
11
  Database = Dry::Schema.Params do
7
- required(:emitter).value(Types::String.enum("database"))
12
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::Database.class_keys))
8
13
  optional(:options).hash(Options)
9
14
  end
10
15
 
11
16
  MISP = Dry::Schema.Params do
12
- required(:emitter).value(Types::String.enum("misp"))
17
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::MISP.class_keys))
13
18
  optional(:url).value(:string)
14
19
  optional(:api_key).value(:string)
15
20
  optional(:options).hash(Options)
16
21
  end
17
22
 
18
23
  TheHive = Dry::Schema.Params do
19
- required(:emitter).value(Types::String.enum("thehive"))
24
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::TheHive.class_keys))
20
25
  optional(:url).value(:string)
21
26
  optional(:api_key).value(:string)
22
27
  optional(:api_version).value(Types::String.enum("v4", "v5")).default("v4")
@@ -24,14 +29,14 @@ module Mihari
24
29
  end
25
30
 
26
31
  Slack = Dry::Schema.Params do
27
- required(:emitter).value(Types::String.enum("slack"))
32
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::Slack.class_keys))
28
33
  optional(:webhook_url).value(:string)
29
34
  optional(:channel).value(:string)
30
35
  optional(:options).hash(Options)
31
36
  end
32
37
 
33
38
  Webhook = Dry::Schema.Params do
34
- required(:emitter).value(Types::String.enum("webhook"))
39
+ required(:emitter).value(Types::String.enum(*Mihari::Emitters::Webhook.class_keys))
35
40
  required(:url).value(:string)
36
41
  optional(:method).value(Types::HTTPRequestMethods).default("POST")
37
42
  optional(:headers).value(:hash).default({})
@@ -39,5 +44,7 @@ module Mihari
39
44
  optional(:options).hash(Options)
40
45
  end
41
46
  end
47
+
48
+ Emitter = Schemas::Emitters.get_or_composition
42
49
  end
43
50
  end
@@ -2,27 +2,34 @@
2
2
 
3
3
  module Mihari
4
4
  module Schemas
5
+ #
6
+ # Enricher schemas
7
+ #
5
8
  module Enrichers
9
+ extend Schemas::Mixins
10
+
6
11
  IPInfo = Dry::Schema.Params do
7
- required(:enricher).value(Types::String.enum("ipinfo"))
12
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::IPInfo.class_keys))
8
13
  optional(:api_key).value(:string)
9
14
  optional(:options).hash(Options)
10
15
  end
11
16
 
12
17
  Whois = Dry::Schema.Params do
13
- required(:enricher).value(Types::String.enum("whois"))
18
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::Whois.class_keys))
14
19
  optional(:options).hash(Options)
15
20
  end
16
21
 
17
22
  Shodan = Dry::Schema.Params do
18
- required(:enricher).value(Types::String.enum("shodan"))
23
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::Shodan.class_keys))
19
24
  optional(:options).hash(Options)
20
25
  end
21
26
 
22
27
  GooglePublicDNS = Dry::Schema.Params do
23
- required(:enricher).value(Types::String.enum("google_public_dns"))
28
+ required(:enricher).value(Types::String.enum(*Mihari::Enrichers::GooglePublicDNS.class_keys))
24
29
  optional(:options).hash(Options)
25
30
  end
26
31
  end
32
+
33
+ Enricher = Schemas::Enrichers.get_or_composition
27
34
  end
28
35
  end
@@ -3,6 +3,10 @@
3
3
  module Dry
4
4
  module Schema
5
5
  module Macros
6
+ #
7
+ # Macros DSL for options with default values
8
+ # (see https://github.com/dry-rb/dry-schema/issues/70)
9
+ #
6
10
  class DSL
7
11
  def default(value)
8
12
  schema_dsl.before(:rule_applier) do |result|