mihari 5.5.0 → 5.6.0
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 +4 -4
- data/docs/analyzers/passivetotal.md +4 -0
- data/docs/analyzers/securitytrails.md +4 -0
- data/docs/analyzers/virustotal.md +4 -0
- data/docs/analyzers/virustotal_intelligence.md +4 -0
- data/docs/emitters/hive.md +1 -1
- data/docs/emitters/slack.md +0 -5
- data/docs/rule.md +1 -4
- data/docs/usage.md +5 -2
- data/frontend/src/components/ErrorMessage.vue +0 -1
- data/frontend/src/components/alert/Alerts.vue +0 -1
- data/frontend/src/components/alert/AlertsWithPagination.vue +0 -1
- data/frontend/src/components/alert/AlertsWrapper.vue +0 -6
- data/frontend/src/components/alert/Form.vue +1 -3
- data/frontend/src/components/artifact/Artifact.vue +0 -17
- data/frontend/src/components/artifact/ArtifactWrapper.vue +0 -2
- data/frontend/src/components/artifact/WhoisRecord.vue +0 -3
- data/frontend/src/components/config/ConfigsWrapper.vue +0 -2
- data/frontend/src/components/rule/EditRule.vue +0 -3
- data/frontend/src/components/rule/EditRuleWrapper.vue +0 -2
- data/frontend/src/components/rule/Form.vue +1 -3
- data/frontend/src/components/rule/NewRule.vue +0 -3
- data/frontend/src/components/rule/Rule.vue +1 -7
- data/frontend/src/components/rule/RuleWrapper.vue +0 -2
- data/frontend/src/components/rule/RulesWrapper.vue +0 -6
- data/frontend/src/swagger.yaml +254 -254
- data/lib/mihari/analyzers/base.rb +4 -41
- data/lib/mihari/analyzers/passivetotal.rb +9 -0
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +24 -59
- data/lib/mihari/analyzers/securitytrails.rb +9 -0
- data/lib/mihari/analyzers/virustotal.rb +11 -2
- data/lib/mihari/analyzers/virustotal_intelligence.rb +16 -0
- data/lib/mihari/analyzers/zoomeye.rb +2 -2
- data/lib/mihari/base.rb +69 -0
- data/lib/mihari/cli/main.rb +36 -0
- data/lib/mihari/commands/alert.rb +6 -33
- data/lib/mihari/commands/rule.rb +7 -12
- data/lib/mihari/commands/search.rb +10 -38
- data/lib/mihari/constants.rb +3 -3
- data/lib/mihari/emitters/base.rb +3 -33
- data/lib/mihari/emitters/database.rb +1 -1
- data/lib/mihari/enrichers/base.rb +2 -33
- data/lib/mihari/enrichers/google_public_dns.rb +9 -0
- data/lib/mihari/schemas/analyzer.rb +24 -24
- data/lib/mihari/schemas/emitter.rb +6 -13
- data/lib/mihari/schemas/enricher.rb +4 -11
- data/lib/mihari/schemas/options.rb +27 -0
- data/lib/mihari/schemas/rule.rb +2 -2
- data/lib/mihari/services/alert_runner.rb +1 -1
- data/lib/mihari/services/rule_runner.rb +1 -11
- data/lib/mihari/types.rb +1 -14
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/{index-33165282.css → index-56fc2187.css} +1 -1
- data/lib/mihari/web/public/assets/{index-b5d817a3.js → index-9cc489e6.js} +2 -2
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari.rb +67 -37
- data/mihari.gemspec +1 -0
- metadata +20 -4
@@ -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,14 @@ 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
|
-
|
23
|
-
@options = options || {}
|
24
|
-
end
|
25
|
-
|
26
|
-
#
|
27
|
-
# @return [Integer]
|
28
|
-
#
|
29
|
-
def retry_interval
|
30
|
-
options[:retry_interval] || Mihari.config.retry_interval
|
31
|
-
end
|
32
|
-
|
33
|
-
#
|
34
|
-
# @return [Boolean]
|
35
|
-
#
|
36
|
-
def retry_exponential_backoff
|
37
|
-
options[:retry_exponential_backoff] || Mihari.config.retry_exponential_backoff
|
38
|
-
end
|
19
|
+
super(options: options)
|
39
20
|
|
40
|
-
|
41
|
-
# @return [Integer]
|
42
|
-
#
|
43
|
-
def retry_times
|
44
|
-
options[:retry_times] || Mihari.config.retry_times
|
21
|
+
@query = query
|
45
22
|
end
|
46
23
|
|
47
24
|
#
|
@@ -68,13 +45,6 @@ module Mihari
|
|
68
45
|
Mihari.config.ignore_error
|
69
46
|
end
|
70
47
|
|
71
|
-
#
|
72
|
-
# @return [Integer, nil]
|
73
|
-
#
|
74
|
-
def timeout
|
75
|
-
options[:timeout]
|
76
|
-
end
|
77
|
-
|
78
48
|
# @return [Array<String>, Array<Mihari::Artifact>]
|
79
49
|
def artifacts
|
80
50
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
@@ -93,7 +63,7 @@ module Mihari
|
|
93
63
|
# No need to set data_type manually
|
94
64
|
# It is set automatically in #initialize
|
95
65
|
artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
|
96
|
-
artifact.source =
|
66
|
+
artifact.source = self.class.class_key
|
97
67
|
artifact
|
98
68
|
end.select(&:valid?).uniq(&:data)
|
99
69
|
end
|
@@ -106,13 +76,6 @@ module Mihari
|
|
106
76
|
Try[StandardError] { normalized_artifacts }.to_result
|
107
77
|
end
|
108
78
|
|
109
|
-
# @return [String]
|
110
|
-
def class_name
|
111
|
-
self.class.to_s.split("::").last
|
112
|
-
end
|
113
|
-
|
114
|
-
alias_method :source, :class_name
|
115
|
-
|
116
79
|
class << self
|
117
80
|
#
|
118
81
|
# Initialize an analyzer by query params
|
@@ -2,46 +2,6 @@
|
|
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
|
-
ENRICHER_TO_CLASS = {
|
39
|
-
"whois" => Enrichers::Whois,
|
40
|
-
"ipinfo" => Enrichers::IPInfo,
|
41
|
-
"shodan" => Enrichers::Shodan,
|
42
|
-
"google_public_dns" => Enrichers::GooglePublicDNS
|
43
|
-
}.freeze
|
44
|
-
|
45
5
|
class Rule
|
46
6
|
include Mixins::FalsePositive
|
47
7
|
|
@@ -126,8 +86,14 @@ module Mihari
|
|
126
86
|
def bulk_emit
|
127
87
|
return [] if enriched_artifacts.empty?
|
128
88
|
|
129
|
-
|
130
|
-
|
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
|
131
97
|
|
132
98
|
Mihari.logger.info "Emission by #{emitter.class} is failed: #{result.failure}" if result.failure?
|
133
99
|
Mihari.logger.info "Emission by #{emitter.class} is succeeded" if result.success?
|
@@ -164,15 +130,14 @@ module Mihari
|
|
164
130
|
#
|
165
131
|
# Get analyzer class
|
166
132
|
#
|
167
|
-
# @param [String]
|
133
|
+
# @param [String] key
|
168
134
|
#
|
169
135
|
# @return [Class<Mihari::Analyzers::Base>] analyzer class
|
170
136
|
#
|
171
|
-
def get_analyzer_class(
|
172
|
-
|
173
|
-
return analyzer if analyzer
|
137
|
+
def get_analyzer_class(key)
|
138
|
+
raise ArgumentError, "#{key} is not supported" unless Mihari.analyzer_to_class.key?(key)
|
174
139
|
|
175
|
-
|
140
|
+
Mihari.analyzer_to_class[key]
|
176
141
|
end
|
177
142
|
|
178
143
|
#
|
@@ -189,15 +154,14 @@ module Mihari
|
|
189
154
|
#
|
190
155
|
# Get emitter class
|
191
156
|
#
|
192
|
-
# @param [String]
|
157
|
+
# @param [String] key
|
193
158
|
#
|
194
159
|
# @return [Class<Mihari::Emitters::Base>] emitter class
|
195
160
|
#
|
196
|
-
def get_emitter_class(
|
197
|
-
|
198
|
-
return emitter if emitter
|
161
|
+
def get_emitter_class(key)
|
162
|
+
raise ArgumentError, "#{key} is not supported" unless Mihari.emitter_to_class.key?(key)
|
199
163
|
|
200
|
-
|
164
|
+
Mihari.emitter_to_class[key]
|
201
165
|
end
|
202
166
|
|
203
167
|
#
|
@@ -219,21 +183,20 @@ module Mihari
|
|
219
183
|
# @return [Array<Mihari::Emitters::Base>]
|
220
184
|
#
|
221
185
|
def valid_emitters
|
222
|
-
emitters.select(&:valid?)
|
186
|
+
@valid_emitters ||= emitters.select(&:valid?)
|
223
187
|
end
|
224
188
|
|
225
189
|
#
|
226
190
|
# Get enricher class
|
227
191
|
#
|
228
|
-
# @param [String]
|
192
|
+
# @param [String] key
|
229
193
|
#
|
230
194
|
# @return [Class<Mihari::Enrichers::Base>] enricher class
|
231
195
|
#
|
232
|
-
def get_enricher_class(
|
233
|
-
|
234
|
-
return enricher if enricher
|
196
|
+
def get_enricher_class(key)
|
197
|
+
raise ArgumentError, "#{key} is not supported" unless Mihari.enricher_to_class.key?(key)
|
235
198
|
|
236
|
-
|
199
|
+
Mihari.enricher_to_class[key]
|
237
200
|
end
|
238
201
|
|
239
202
|
#
|
@@ -258,7 +221,9 @@ module Mihari
|
|
258
221
|
analyzers.map do |analyzer|
|
259
222
|
next if analyzer.configured?
|
260
223
|
|
261
|
-
|
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."
|
262
227
|
raise ConfigurationError, message
|
263
228
|
end
|
264
229
|
end
|
@@ -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,
|
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,
|
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
|
#
|
@@ -73,9 +73,9 @@ module Mihari
|
|
73
73
|
data = match["ip"]
|
74
74
|
|
75
75
|
if data.is_a?(Array)
|
76
|
-
data.map { |d| Artifact.new(data: d,
|
76
|
+
data.map { |d| Artifact.new(data: d, metadata: match) }
|
77
77
|
else
|
78
|
-
Artifact.new(data: data,
|
78
|
+
Artifact.new(data: data, metadata: match)
|
79
79
|
end
|
80
80
|
end.flatten
|
81
81
|
end
|
data/lib/mihari/base.rb
ADDED
@@ -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
|
data/lib/mihari/cli/main.rb
CHANGED
@@ -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
|
|
@@ -14,30 +14,14 @@ module Mihari
|
|
14
14
|
#
|
15
15
|
def add(path)
|
16
16
|
Mihari::Database.with_db_connection do
|
17
|
-
builder =
|
17
|
+
builder = Services::AlertBuilder.new(path)
|
18
18
|
|
19
19
|
run_proxy_l = ->(proxy) { run_proxy proxy }
|
20
|
-
|
20
|
+
result = builder.result.bind(run_proxy_l)
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
alert = result.value!
|
26
|
-
data = Mihari::Entities::Alert.represent(alert)
|
27
|
-
puts JSON.pretty_generate(data.as_json)
|
28
|
-
return
|
29
|
-
end
|
30
|
-
|
31
|
-
failure = result.failure
|
32
|
-
case failure
|
33
|
-
when ValidationError
|
34
|
-
Mihari.logger.error "Failed to parse the input as an alert:"
|
35
|
-
Mihari.logger.error JSON.pretty_generate(failure.errors.to_h)
|
36
|
-
when StandardError
|
37
|
-
raise failure
|
38
|
-
else
|
39
|
-
Mihari.logger.info failure
|
40
|
-
end
|
22
|
+
alert = result.value!
|
23
|
+
data = Entities::Alert.represent(alert)
|
24
|
+
puts JSON.pretty_generate(data.as_json)
|
41
25
|
end
|
42
26
|
end
|
43
27
|
|
@@ -47,21 +31,10 @@ module Mihari
|
|
47
31
|
#
|
48
32
|
def run_proxy(proxy)
|
49
33
|
Dry::Monads::Try[StandardError] do
|
50
|
-
runner =
|
34
|
+
runner = Services::AlertRunner.new(proxy)
|
51
35
|
runner.run
|
52
36
|
end.to_result
|
53
37
|
end
|
54
|
-
|
55
|
-
#
|
56
|
-
# @param [Mihari::Alert, nil] alert_or_nil
|
57
|
-
#
|
58
|
-
def check_nil(alert_or_nil)
|
59
|
-
if alert_or_nil.nil?
|
60
|
-
Failure "There is no new artifact found"
|
61
|
-
else
|
62
|
-
Success alert_or_nil
|
63
|
-
end
|
64
|
-
end
|
65
38
|
end
|
66
39
|
end
|
67
40
|
end
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -8,7 +8,7 @@ module Mihari
|
|
8
8
|
class << self
|
9
9
|
def included(thor)
|
10
10
|
thor.class_eval do
|
11
|
-
include Dry::Monads[:
|
11
|
+
include Dry::Monads[:try, :result]
|
12
12
|
|
13
13
|
desc "validate [PATH]", "Validate a rule file"
|
14
14
|
#
|
@@ -18,16 +18,11 @@ module Mihari
|
|
18
18
|
#
|
19
19
|
def validate(path)
|
20
20
|
res = Dry::Monads::Try[ValidationError] do
|
21
|
-
Services::RuleProxy.from_yaml
|
22
|
-
end.fmap do |rule|
|
23
|
-
Mihari.logger.info "Valid format. The input is parsed as the following:"
|
24
|
-
Mihari.logger.info rule.data.to_yaml
|
21
|
+
Services::RuleProxy.from_yaml File.read(path)
|
25
22
|
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
Mihari.logger.error "Failed to parse the input as a rule:"
|
30
|
-
Mihari.logger.error JSON.pretty_generate(res.exception.errors.to_h)
|
24
|
+
rule = res.value!
|
25
|
+
puts rule.data.to_yaml
|
31
26
|
end
|
32
27
|
|
33
28
|
desc "init [PATH]", "Initialize a new rule file"
|
@@ -43,14 +38,14 @@ module Mihari
|
|
43
38
|
|
44
39
|
initialize_rule path
|
45
40
|
|
46
|
-
|
41
|
+
puts "A new rule file has been initialized: #{path}."
|
47
42
|
end
|
48
43
|
|
49
44
|
no_commands do
|
50
45
|
#
|
51
46
|
# @return [Mihari::Services::Rule]
|
52
47
|
#
|
53
|
-
def
|
48
|
+
def rule
|
54
49
|
Services::RuleProxy.from_yaml File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
|
55
50
|
end
|
56
51
|
|
@@ -63,7 +58,7 @@ module Mihari
|
|
63
58
|
# @return [nil]
|
64
59
|
#
|
65
60
|
def initialize_rule(path, files = Dry::Files.new)
|
66
|
-
files.write(path,
|
61
|
+
files.write(path, rule.yaml)
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
@@ -6,10 +6,10 @@ module Mihari
|
|
6
6
|
class << self
|
7
7
|
def included(thor)
|
8
8
|
thor.class_eval do
|
9
|
-
include Dry::Monads[:
|
9
|
+
include Dry::Monads[:try, :result]
|
10
10
|
|
11
|
-
desc "search [PATH_OR_ID]", "Search by a rule"
|
12
|
-
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force
|
11
|
+
desc "search [PATH_OR_ID]", "Search by a rule (Outputs null if there is no new finding)"
|
12
|
+
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force overwriting a rule"
|
13
13
|
#
|
14
14
|
# Search by a rule
|
15
15
|
#
|
@@ -21,27 +21,12 @@ module Mihari
|
|
21
21
|
|
22
22
|
check_diff_l = ->(rule) { check_diff rule }
|
23
23
|
update_and_run_l = ->(runner) { update_and_run runner }
|
24
|
-
check_nil_l = ->(alert_or_nil) { check_nil alert_or_nil }
|
25
24
|
|
26
|
-
result = builder.result.bind(check_diff_l).bind(update_and_run_l)
|
25
|
+
result = builder.result.bind(check_diff_l).bind(update_and_run_l)
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
puts JSON.pretty_generate(data.as_json)
|
32
|
-
return
|
33
|
-
end
|
34
|
-
|
35
|
-
failure = result.failure
|
36
|
-
case failure
|
37
|
-
when ValidationError
|
38
|
-
Mihari.logger.error "Failed to parse the input as a rule:"
|
39
|
-
Mihari.logger.error JSON.pretty_generate(failure.errors.to_h)
|
40
|
-
when StandardError
|
41
|
-
raise failure
|
42
|
-
else
|
43
|
-
Mihari.logger.info failure
|
44
|
-
end
|
27
|
+
alert = result.value!
|
28
|
+
data = Entities::Alert.represent(alert)
|
29
|
+
puts JSON.pretty_generate(data.as_json)
|
45
30
|
end
|
46
31
|
end
|
47
32
|
|
@@ -51,27 +36,14 @@ module Mihari
|
|
51
36
|
#
|
52
37
|
def check_diff(rule)
|
53
38
|
force_overwrite = options["force_overwrite"] || false
|
54
|
-
|
55
|
-
|
39
|
+
message = "There is a diff in the rule. Are you sure you want to overwrite the rule? (y/n)"
|
40
|
+
runner = Services::RuleRunner.new(rule)
|
56
41
|
|
57
|
-
if runner.diff? && !force_overwrite && !yes?(message)
|
58
|
-
return Failure("Stop overwriting the rule (#{rule.id})")
|
59
|
-
end
|
42
|
+
exit 0 if runner.diff? && !force_overwrite && !yes?(message)
|
60
43
|
|
61
44
|
Success runner
|
62
45
|
end
|
63
46
|
|
64
|
-
#
|
65
|
-
# @param [Mihari::Alert, nil] alert_or_nil
|
66
|
-
#
|
67
|
-
def check_nil(alert_or_nil)
|
68
|
-
if alert_or_nil.nil?
|
69
|
-
Failure "There is no new artifact found"
|
70
|
-
else
|
71
|
-
Success alert_or_nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
47
|
#
|
76
48
|
# @param [Mihari::Services::RuleRunner] runner
|
77
49
|
#
|
data/lib/mihari/constants.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
# @return [Array<String>]
|
5
|
-
DEFAULT_DATA_TYPES =
|
5
|
+
DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
|
6
6
|
|
7
7
|
# @return [Array<Hash>]
|
8
|
-
DEFAULT_EMITTERS = %w[database
|
8
|
+
DEFAULT_EMITTERS = %w[database].map { |name| { emitter: name } }.freeze
|
9
9
|
|
10
10
|
# @return [Array<Hash>]
|
11
|
-
DEFAULT_ENRICHERS =
|
11
|
+
DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
|
12
12
|
end
|