mihari 5.2.0 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) 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/commands/web.rb +9 -2
  13. data/lib/mihari/mixins/error_notification.rb +0 -2
  14. data/lib/mihari/models/artifact.rb +1 -1
  15. data/lib/mihari/schemas/rule.rb +2 -17
  16. data/lib/mihari/structs/censys.rb +167 -11
  17. data/lib/mihari/structs/config.rb +28 -0
  18. data/lib/mihari/structs/google_public_dns.rb +39 -1
  19. data/lib/mihari/structs/greynoise.rb +93 -6
  20. data/lib/mihari/structs/ipinfo.rb +40 -0
  21. data/lib/mihari/structs/onyphe.rb +88 -6
  22. data/lib/mihari/structs/rule.rb +4 -2
  23. data/lib/mihari/structs/shodan.rb +138 -4
  24. data/lib/mihari/structs/urlscan.rb +98 -1
  25. data/lib/mihari/structs/virustotal_intelligence.rb +96 -1
  26. data/lib/mihari/version.rb +1 -1
  27. data/lib/mihari/web/app.rb +2 -2
  28. data/lib/mihari/web/public/assets/index-cbe1734c.js +50 -0
  29. data/lib/mihari/web/public/assets/index-eed1bcd8.css +5 -0
  30. data/lib/mihari/web/public/index.html +2 -2
  31. data/lib/mihari.rb +1 -0
  32. data/mihari.gemspec +13 -12
  33. metadata +41 -42
  34. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  35. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
  36. data/.github/workflows/test.yml +0 -90
  37. data/config/pre_commit.yml +0 -3
  38. data/docker/Dockerfile +0 -14
  39. data/examples/ipinfo_hosted_domains.rb +0 -45
  40. data/images/Tines-Full_Logo-Tines_Black.png +0 -0
  41. data/images/alert.png +0 -0
  42. data/images/logo.png +0 -0
  43. data/images/misp.png +0 -0
  44. data/images/overview.jpg +0 -0
  45. data/images/slack.png +0 -0
  46. data/images/tines.png +0 -0
  47. data/images/web_alerts.png +0 -0
  48. data/images/web_config.png +0 -0
  49. data/lib/mihari/commands/searcher.rb +0 -61
  50. data/lib/mihari/web/public/assets/index-9948ee35.js +0 -50
  51. data/lib/mihari/web/public/assets/index-d88cc3f1.css +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c98bfd494945e88b92bbf939e9ea94d5d4161cc35ae7d7a0d01ead7a30a1f09
4
- data.tar.gz: 4794020ea3ad71fe70016030abd52063d523c7bbf7a31d9129fe8a2b76f43bc5
3
+ metadata.gz: 77de9f96ff14a64b2ab7a492fff36ed1515df7def7b18b5d81bac6785a5b75c0
4
+ data.tar.gz: 9e99189740e4e3b6ee6af97cc09fbd2af1f5395c047df925f528721f2c68595d
5
5
  SHA512:
6
- metadata.gz: aff9062a772dd86fd202e9112d92c923c815dba5d15b83b567c8ff00a72803ece1674201daeffc2934beba54f7d9ff2e929e1d570de9fe21c503bd89c6648c13
7
- data.tar.gz: 8ab4db7d0385ac7301c158c95cba0855cc002c9e115911e497011e0705c97fdc5205a6f47e07917d867ba5270f3e4b24f80c38a407c34a2a3db717b9fd5ef732
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
@@ -13,12 +13,19 @@ module Mihari
13
13
  method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
14
14
  method_option :hide_config_values, type: :boolean, default: false,
15
15
  desc: "Whether to hide config values or not"
16
+ method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
16
17
  def web
17
18
  Mihari.config.hide_config_values = options["hide_config_values"]
18
19
  # set rack env as production
19
20
  ENV["RACK_ENV"] ||= "production"
20
- Mihari::App.run!(port: options["port"], host: options["host"], threads: options["threads"],
21
- verbose: options["verbose"], worker_timeout: options["worker_timeout"])
21
+ Mihari::App.run!(
22
+ port: options["port"],
23
+ host: options["host"],
24
+ threads: options["threads"],
25
+ verbose: options["verbose"],
26
+ worker_timeout: options["worker_timeout"],
27
+ open: options["open"]
28
+ )
22
29
  end
23
30
  end
24
31
  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)