mihari 4.12.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/Steepfile +0 -1
  3. data/lib/mihari/analyzers/base.rb +18 -37
  4. data/lib/mihari/analyzers/rule.rb +23 -36
  5. data/lib/mihari/cli/main.rb +6 -11
  6. data/lib/mihari/commands/initializer.rb +47 -0
  7. data/lib/mihari/commands/searcher.rb +57 -0
  8. data/lib/mihari/commands/validator.rb +2 -2
  9. data/lib/mihari/constants.rb +3 -3
  10. data/lib/mihari/database.rb +52 -87
  11. data/lib/mihari/emitters/database.rb +16 -7
  12. data/lib/mihari/emitters/misp.rb +13 -5
  13. data/lib/mihari/emitters/slack.rb +15 -8
  14. data/lib/mihari/emitters/the_hive.rb +42 -21
  15. data/lib/mihari/emitters/webhook.rb +99 -31
  16. data/lib/mihari/entities/alert.rb +7 -5
  17. data/lib/mihari/entities/artifact.rb +20 -8
  18. data/lib/mihari/entities/config.rb +2 -6
  19. data/lib/mihari/entities/rule.rb +8 -0
  20. data/lib/mihari/http.rb +13 -13
  21. data/lib/mihari/mixins/{disallowed_data_value.rb → falsepositive.rb} +8 -8
  22. data/lib/mihari/models/alert.rb +2 -15
  23. data/lib/mihari/models/artifact.rb +28 -17
  24. data/lib/mihari/models/rule.rb +7 -13
  25. data/lib/mihari/schemas/emitter.rb +6 -8
  26. data/lib/mihari/schemas/rule.rb +11 -13
  27. data/lib/mihari/structs/config.rb +41 -0
  28. data/lib/mihari/structs/filters.rb +2 -2
  29. data/lib/mihari/structs/rule.rb +94 -85
  30. data/lib/mihari/templates/rule.yml.erb +5 -23
  31. data/lib/mihari/types.rb +1 -1
  32. data/lib/mihari/version.rb +1 -1
  33. data/lib/mihari/web/api.rb +0 -2
  34. data/lib/mihari/web/endpoints/alerts.rb +11 -3
  35. data/lib/mihari/web/endpoints/configs.rb +1 -6
  36. data/lib/mihari/web/endpoints/rules.rb +27 -15
  37. data/lib/mihari/web/public/assets/{fa-brands-400-b1d1c1b0.ttf → fa-brands-400-2ef6fdde.ttf} +0 -0
  38. data/lib/mihari/web/public/assets/fa-brands-400-f4617423.woff2 +0 -0
  39. data/lib/mihari/web/public/assets/fa-regular-400-12dea17b.ttf +0 -0
  40. data/lib/mihari/web/public/assets/fa-regular-400-7ba24c41.woff2 +0 -0
  41. data/lib/mihari/web/public/assets/fa-solid-900-67a880b4.ttf +0 -0
  42. data/lib/mihari/web/public/assets/fa-solid-900-e2c5cf54.woff2 +0 -0
  43. data/lib/mihari/web/public/assets/fa-v4compatibility-7c377405.woff2 +0 -0
  44. data/lib/mihari/web/public/assets/fa-v4compatibility-8d9500e8.ttf +0 -0
  45. data/lib/mihari/web/public/assets/{index-07aa1ba2.css → index-625e95fe.css} +3 -3
  46. data/lib/mihari/web/public/assets/index-63900d73.js +50 -0
  47. data/lib/mihari/web/public/index.html +2 -2
  48. data/lib/mihari/web/public/redoc-static.html +26 -27
  49. data/lib/mihari.rb +11 -21
  50. data/mihari.gemspec +4 -4
  51. metadata +25 -111
  52. data/lib/mihari/cli/init.rb +0 -11
  53. data/lib/mihari/cli/validator.rb +0 -11
  54. data/lib/mihari/commands/init.rb +0 -51
  55. data/lib/mihari/commands/search.rb +0 -63
  56. data/lib/mihari/emitters/http.rb +0 -127
  57. data/lib/mihari/entities/source.rb +0 -9
  58. data/lib/mihari/status.rb +0 -55
  59. data/lib/mihari/web/endpoints/sources.rb +0 -19
  60. data/lib/mihari/web/public/assets/fa-brands-400-c61287c2.woff2 +0 -0
  61. data/lib/mihari/web/public/assets/fa-regular-400-5da313b0.woff2 +0 -0
  62. data/lib/mihari/web/public/assets/fa-regular-400-d7b19fe2.ttf +0 -0
  63. data/lib/mihari/web/public/assets/fa-solid-900-8f06540f.woff2 +0 -0
  64. data/lib/mihari/web/public/assets/fa-solid-900-e4f6a7e9.ttf +0 -0
  65. data/lib/mihari/web/public/assets/fa-v4compatibility-2ddb3b41.ttf +0 -0
  66. data/lib/mihari/web/public/assets/fa-v4compatibility-f46715c9.woff2 +0 -0
  67. data/lib/mihari/web/public/assets/index-a7fe697b.js +0 -63
  68. data/sig/lib/mihari/analyzers/base.rbs +0 -90
  69. data/sig/lib/mihari/analyzers/binaryedge.rbs +0 -26
  70. data/sig/lib/mihari/analyzers/censys.rbs +0 -41
  71. data/sig/lib/mihari/analyzers/circl.rbs +0 -31
  72. data/sig/lib/mihari/analyzers/crtsh.rbs +0 -17
  73. data/sig/lib/mihari/analyzers/dnpedia.rbs +0 -15
  74. data/sig/lib/mihari/analyzers/dnstwister.rbs +0 -25
  75. data/sig/lib/mihari/analyzers/feed.rbs +0 -20
  76. data/sig/lib/mihari/analyzers/onyphe.rbs +0 -34
  77. data/sig/lib/mihari/analyzers/otx.rbs +0 -33
  78. data/sig/lib/mihari/analyzers/passivetotal.rbs +0 -35
  79. data/sig/lib/mihari/analyzers/pulsedive.rbs +0 -27
  80. data/sig/lib/mihari/analyzers/rule.rbs +0 -68
  81. data/sig/lib/mihari/analyzers/securitytrails.rbs +0 -33
  82. data/sig/lib/mihari/analyzers/shodan.rbs +0 -36
  83. data/sig/lib/mihari/analyzers/urlscan.rbs +0 -31
  84. data/sig/lib/mihari/analyzers/virustotal.rbs +0 -31
  85. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +0 -33
  86. data/sig/lib/mihari/analyzers/zoomeye.rbs +0 -35
  87. data/sig/lib/mihari/cli/base.rbs +0 -9
  88. data/sig/lib/mihari/cli/init.rbs +0 -7
  89. data/sig/lib/mihari/cli/main.rbs +0 -9
  90. data/sig/lib/mihari/cli/validator.rbs +0 -7
  91. data/sig/lib/mihari/commands/init.rbs +0 -9
  92. data/sig/lib/mihari/commands/json.rbs +0 -7
  93. data/sig/lib/mihari/commands/search.rbs +0 -35
  94. data/sig/lib/mihari/commands/validator.rbs +0 -9
  95. data/sig/lib/mihari/commands/web.rbs +0 -7
  96. data/sig/lib/mihari/constants.rbs +0 -5
  97. data/sig/lib/mihari/database.rbs +0 -25
  98. data/sig/lib/mihari/emitters/base.rbs +0 -18
  99. data/sig/lib/mihari/emitters/database.rbs +0 -9
  100. data/sig/lib/mihari/emitters/http.rbs +0 -35
  101. data/sig/lib/mihari/emitters/misp.rbs +0 -34
  102. data/sig/lib/mihari/emitters/slack.rbs +0 -73
  103. data/sig/lib/mihari/emitters/stdout.rbs +0 -9
  104. data/sig/lib/mihari/emitters/the_hive.rbs +0 -32
  105. data/sig/lib/mihari/emitters/webhook.rbs +0 -20
  106. data/sig/lib/mihari/enrichers/base.rbs +0 -12
  107. data/sig/lib/mihari/enrichers/google_public_dns.rbs +0 -18
  108. data/sig/lib/mihari/enrichers/ipinfo.rbs +0 -16
  109. data/sig/lib/mihari/errors.rbs +0 -10
  110. data/sig/lib/mihari/feed/parser.rbs +0 -11
  111. data/sig/lib/mihari/feed/reader.rbs +0 -56
  112. data/sig/lib/mihari/http.rbs +0 -64
  113. data/sig/lib/mihari/mixins/autonomous_system.rbs +0 -14
  114. data/sig/lib/mihari/mixins/configurable.rbs +0 -30
  115. data/sig/lib/mihari/mixins/configuration.rbs +0 -45
  116. data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -23
  117. data/sig/lib/mihari/mixins/error_notification.rbs +0 -12
  118. data/sig/lib/mihari/mixins/hash.rbs +0 -14
  119. data/sig/lib/mihari/mixins/refang.rbs +0 -14
  120. data/sig/lib/mihari/mixins/retriable.rbs +0 -15
  121. data/sig/lib/mihari/models/alert.rbs +0 -18
  122. data/sig/lib/mihari/models/artifact.rbs +0 -69
  123. data/sig/lib/mihari/models/autonomous_system.rbs +0 -14
  124. data/sig/lib/mihari/models/cpe.rbs +0 -7
  125. data/sig/lib/mihari/models/dns.rbs +0 -19
  126. data/sig/lib/mihari/models/geolocation.rbs +0 -15
  127. data/sig/lib/mihari/models/port.rbs +0 -7
  128. data/sig/lib/mihari/models/reverse_dns.rbs +0 -14
  129. data/sig/lib/mihari/models/rule.rbs +0 -17
  130. data/sig/lib/mihari/models/tag.rbs +0 -5
  131. data/sig/lib/mihari/models/tagging.rbs +0 -4
  132. data/sig/lib/mihari/models/whois.rbs +0 -66
  133. data/sig/lib/mihari/status.rbs +0 -25
  134. data/sig/lib/mihari/structs/censys.rbs +0 -58
  135. data/sig/lib/mihari/structs/filters.rbs +0 -40
  136. data/sig/lib/mihari/structs/google_public_dns.rbs +0 -21
  137. data/sig/lib/mihari/structs/greynoise.rbs +0 -30
  138. data/sig/lib/mihari/structs/ipinfo.rbs +0 -17
  139. data/sig/lib/mihari/structs/onyphe.rbs +0 -25
  140. data/sig/lib/mihari/structs/rule.rbs +0 -57
  141. data/sig/lib/mihari/structs/shodan.rbs +0 -30
  142. data/sig/lib/mihari/structs/urlscan.rbs +0 -28
  143. data/sig/lib/mihari/structs/virustotal_intelligence.rbs +0 -33
  144. data/sig/lib/mihari/type_checker.rbs +0 -48
  145. data/sig/lib/mihari/types.rbs +0 -23
  146. data/sig/lib/mihari/version.rbs +0 -3
  147. data/sig/lib/mihari/web/app.rbs +0 -5
  148. data/sig/lib/mihari.rbs +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16412e44a6aa5eb9fb7022aa531b950249df76f074136e71c6621e5f2d3c7d44
4
- data.tar.gz: 403f780911934e891ef072c87a3a6ae48d31f1854c8806d4c9ece884faf9ec62
3
+ metadata.gz: b7944fcbb2ef6b1ff7fccbe5c8158bd21a186b05e8fae70a6700256dce10adbb
4
+ data.tar.gz: 36605153506952b323be6e3a7646fd4446f280943fc6c587d7885e8eec413c33
5
5
  SHA512:
6
- metadata.gz: edde8ae3fb93a7e3719788c6727782f7f9a9e2ae53eaa9d804342e545e0ca5e9ec6bb96d633f9c26a33f98e4addaf8693b6da37de8930f8770847a531665baa0
7
- data.tar.gz: afda1bef59be058cbb5972d771bf7615820cc4588cae6cc53c2f2d39333e02edce9a3165dee30caf0996195ab09565392316d116040ce5cc93dbdac28b04c239
6
+ metadata.gz: ba53c1fb987ffd933017ccc64a3a72adc032de81a377e34684c4927304d295d85531ad11e796abd3c17489411fcf15eedd494d7ff7a8b7d0c53fdeb511eb5a8d
7
+ data.tar.gz: 6dff687f32dfef7f0cc19b76cba77a6233ebbefa9b90f522f37092468370eb5abb0e7f185d86f74077944cb0c76609b01390e7878ca494a82419ac44e59d93e5
data/Steepfile CHANGED
@@ -1,5 +1,4 @@
1
1
  target :lib do
2
- signature "sig"
3
2
  check "lib"
4
3
 
5
4
  repo_path "vendor/rbs/gem_rbs_collection/gems"
@@ -5,18 +5,19 @@ module Mihari
5
5
  class Base
6
6
  extend Dry::Initializer
7
7
 
8
+ option :rule, default: proc {}
9
+
8
10
  include Mixins::AutonomousSystem
9
11
  include Mixins::Configurable
10
- include Mixins::Database
11
12
  include Mixins::Retriable
12
13
 
13
- attr_accessor :ignore_old_artifacts, :ignore_threshold
14
+ # @return [Mihari::Structs::Rule, nil]
15
+ attr_reader :rule
14
16
 
15
17
  def initialize(*args, **kwargs)
16
- super
18
+ super(*args, **kwargs)
17
19
 
18
- @ignore_old_artifacts = false
19
- @ignore_threshold = 0
20
+ @base_time = Time.now.utc
20
21
  end
21
22
 
22
23
  # @return [Array<String>, Array<Mihari::Artifact>]
@@ -24,26 +25,11 @@ module Mihari
24
25
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
25
26
  end
26
27
 
27
- # @return [String]
28
- def title
29
- self.class.to_s.split("::").last.to_s
30
- end
31
-
32
- # @return [String]
33
- def description
34
- raise NotImplementedError, "You must implement #{self.class}##{__method__}"
35
- end
36
-
37
28
  # @return [String]
38
29
  def source
39
30
  self.class.to_s.split("::").last.to_s
40
31
  end
41
32
 
42
- # @return [Array<String>]
43
- def tags
44
- []
45
- end
46
-
47
33
  #
48
34
  # Set artifacts & run emitters in parallel
49
35
  #
@@ -55,16 +41,14 @@ module Mihari
55
41
  raise ConfigurationError, "#{class_name} is not configured correctly"
56
42
  end
57
43
 
58
- with_db_connection do
59
- set_enriched_artifacts
60
-
61
- responses = Parallel.map(valid_emitters) do |emitter|
62
- run_emitter emitter
63
- end
44
+ set_enriched_artifacts
64
45
 
65
- # returns Mihari::Alert created by the database emitter
66
- responses.find { |res| res.is_a?(Mihari::Alert) }
46
+ responses = Parallel.map(valid_emitters) do |emitter|
47
+ run_emitter emitter
67
48
  end
49
+
50
+ # returns Mihari::Alert created by the database emitter
51
+ responses.find { |res| res.is_a?(Mihari::Alert) }
68
52
  end
69
53
 
70
54
  #
@@ -77,13 +61,7 @@ module Mihari
77
61
  def run_emitter(emitter)
78
62
  return if enriched_artifacts.empty?
79
63
 
80
- alert_or_something = emitter.run(
81
- title: title,
82
- description: description,
83
- artifacts: enriched_artifacts,
84
- source: source,
85
- tags: tags
86
- )
64
+ alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
87
65
 
88
66
  Mihari.logger.info "Emission by #{emitter.class} is succedded"
89
67
 
@@ -112,7 +90,10 @@ module Mihari
112
90
  # No need to set data_type manually
113
91
  # It is set automatically in #initialize
114
92
  artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
115
- end.select(&:valid?).uniq(&:data)
93
+ end.select(&:valid?).uniq(&:data).map do |artifact|
94
+ artifact.rule_id = rule&.id
95
+ artifact
96
+ end
116
97
  end
117
98
 
118
99
  private
@@ -124,7 +105,7 @@ module Mihari
124
105
  #
125
106
  def unique_artifacts
126
107
  @unique_artifacts ||= normalized_artifacts.select do |artifact|
127
- artifact.unique?(ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold)
108
+ artifact.unique?(base_time: @base_time, artifact_lifetime: rule&.artifact_lifetime)
128
109
  end
129
110
  end
130
111
 
@@ -29,38 +29,21 @@ module Mihari
29
29
 
30
30
  EMITTER_TO_CLASS = {
31
31
  "database" => Emitters::Database,
32
- "http" => Emitters::HTTP,
33
32
  "misp" => Emitters::MISP,
34
33
  "slack" => Emitters::Slack,
35
34
  "the_hive" => Emitters::TheHive,
36
35
  "webhook" => Emitters::Webhook
37
36
  }.freeze
38
37
 
39
- class Rule < Base
40
- include Mixins::DisallowedDataValue
41
-
42
- option :title
43
- option :description
44
- option :queries
45
-
46
- option :id, default: proc { "" }
47
- option :tags, default: proc { [] }
48
- option :allowed_data_types, default: proc { ALLOWED_DATA_TYPES }
49
- option :disallowed_data_values, default: proc { [] }
38
+ # @return [Mihari::Structs::Rule]
39
+ attr_reader :rule
50
40
 
51
- option :emitters, optional: true
52
- option :enrichers, optional: true
53
-
54
- attr_reader :source
41
+ class Rule < Base
42
+ include Mixins::FalsePositive
55
43
 
56
44
  def initialize(**kwargs)
57
45
  super(**kwargs)
58
46
 
59
- @source = id
60
-
61
- @emitters = emitters || DEFAULT_EMITTERS
62
- @enrichers = enrichers || DEFAULT_ENRICHERS
63
-
64
47
  validate_analyzer_configurations
65
48
  end
66
49
 
@@ -72,7 +55,7 @@ module Mihari
72
55
  def artifacts
73
56
  artifacts = []
74
57
 
75
- queries.each do |original_params|
58
+ rule.queries.each do |original_params|
76
59
  parmas = original_params.deep_dup
77
60
 
78
61
  analyzer_name = parmas[:analyzer]
@@ -83,8 +66,12 @@ module Mihari
83
66
  # set interval in the top level
84
67
  options = parmas[:options] || {}
85
68
  interval = options[:interval]
69
+
86
70
  parmas[:interval] = interval if interval
87
71
 
72
+ # set rule
73
+ parmas[:rule] = rule
74
+
88
75
  analyzer = klass.new(query, **parmas)
89
76
 
90
77
  # Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
@@ -106,9 +93,9 @@ module Mihari
106
93
  #
107
94
  def normalized_artifacts
108
95
  @normalized_artifacts ||= artifacts.uniq(&:data).select(&:valid?).select do |artifact|
109
- allowed_data_types.include? artifact.data_type
96
+ rule.data_types.include? artifact.data_type
110
97
  end.reject do |artifact|
111
- disallowed_data_value? artifact.data
98
+ falsepositive? artifact.data
112
99
  end
113
100
  end
114
101
 
@@ -119,7 +106,7 @@ module Mihari
119
106
  #
120
107
  def enriched_artifacts
121
108
  @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
122
- enrichers.each do |enricher|
109
+ rule.enrichers.each do |enricher|
123
110
  artifact.enrich_by_enricher(enricher[:enricher])
124
111
  end
125
112
 
@@ -132,22 +119,22 @@ module Mihari
132
119
  #
133
120
  # @return [Array<Regexp, String>]
134
121
  #
135
- def normalized_disallowed_data_values
136
- @normalized_disallowed_data_values ||= disallowed_data_values.map { |v| normalize_disallowed_data_value v }
122
+ def normalized_falsepositives
123
+ @normalized_falsepositives ||= rule.falsepositives.map { |v| normalize_falsepositive v }
137
124
  end
138
125
 
139
126
  #
140
- # Check whether a value is a disallowed data value or not
127
+ # Check whether a value is a falsepositive value or not
141
128
  #
142
129
  # @return [Boolean]
143
130
  #
144
- def disallowed_data_value?(value)
145
- return true if normalized_disallowed_data_values.include?(value)
131
+ def falsepositive?(value)
132
+ return true if normalized_falsepositives.include?(value)
146
133
 
147
- normalized_disallowed_data_values.select do |disallowed_data_value|
148
- disallowed_data_value.is_a?(Regexp)
149
- end.any? do |disallowed_data_value|
150
- disallowed_data_value.match?(value)
134
+ normalized_falsepositives.select do |falsepositive|
135
+ falsepositive.is_a?(Regexp)
136
+ end.any? do |falseposistive|
137
+ falseposistive.match?(value)
151
138
  end
152
139
  end
153
140
 
@@ -168,7 +155,7 @@ module Mihari
168
155
  end
169
156
 
170
157
  def valid_emitters
171
- @valid_emitters ||= emitters.filter_map do |original_params|
158
+ @valid_emitters ||= rule.emitters.filter_map do |original_params|
172
159
  params = original_params.deep_dup
173
160
 
174
161
  name = params[:emitter]
@@ -199,7 +186,7 @@ module Mihari
199
186
  # Validate configuration of analyzers
200
187
  #
201
188
  def validate_analyzer_configurations
202
- queries.each do |params|
189
+ rule.queries.each do |params|
203
190
  analyzer_name = params[:analyzer]
204
191
  klass = get_analyzer_class(analyzer_name)
205
192
 
@@ -3,28 +3,23 @@
3
3
  require "thor"
4
4
 
5
5
  # Commands
6
- require "mihari/commands/search"
6
+ require "mihari/commands/initializer"
7
+ require "mihari/commands/searcher"
8
+ require "mihari/commands/validator"
7
9
  require "mihari/commands/version"
8
10
  require "mihari/commands/web"
9
11
 
10
12
  # CLIs
11
13
  require "mihari/cli/base"
12
14
 
13
- require "mihari/cli/init"
14
- require "mihari/cli/validator"
15
-
16
15
  module Mihari
17
16
  module CLI
18
17
  class Main < Base
19
- include Mihari::Commands::Search
18
+ include Mihari::Commands::Searcher
20
19
  include Mihari::Commands::Version
21
20
  include Mihari::Commands::Web
22
-
23
- desc "init", "Sub commands to initialize a rule"
24
- subcommand "init", Initialization
25
-
26
- desc "validate", "Sub commands to validate format of a rule"
27
- subcommand "validate", Validator
21
+ include Mihari::Commands::Validator
22
+ include Mihari::Commands::Initializer
28
23
  end
29
24
  end
30
25
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Mihari
6
+ module Commands
7
+ module Initializer
8
+ def self.included(thor)
9
+ thor.class_eval do
10
+ desc "init", "Initialize a new rule"
11
+ method_option :path, type: :string, default: "./rule.yml"
12
+ def init
13
+ path = options["path"]
14
+
15
+ warning = "#{path} exists. Do you want to overwrite it? (y/n)"
16
+ return if Pathname(path).exist? && !(yes? warning)
17
+
18
+ initialize_rule path
19
+
20
+ Mihari.logger.info "A new rule is initialized as #{path}."
21
+ end
22
+
23
+ no_commands do
24
+ #
25
+ # @return [Mihari::Structs::Rule]
26
+ #
27
+ def rule_template
28
+ Structs::Rule.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
29
+ end
30
+
31
+ #
32
+ # Create a new rule
33
+ #
34
+ # @param [String] path
35
+ # @param [Dry::Files] files
36
+ #
37
+ # @return [nil]
38
+ #
39
+ def initialize_rule(path, files = Dry::Files.new)
40
+ files.write(path, rule_template.yaml)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module Searcher
6
+ include Mixins::Database
7
+ include Mixins::ErrorNotification
8
+
9
+ def self.included(thor)
10
+ thor.class_eval do
11
+ desc "search [PATH]", "Search by a rule"
12
+ method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
13
+ def search(path_or_id)
14
+ with_db_connection do
15
+ rule = Structs::Rule.from_path_or_id path_or_id
16
+
17
+ # validate
18
+ begin
19
+ rule.validate!
20
+ rescue RuleValidationError
21
+ return
22
+ end
23
+
24
+ force_overwrite = options["force_overwrite"] || false
25
+
26
+ begin
27
+ rule_model = Mihari::Rule.find(rule.id)
28
+ has_change = rule_model.data != rule.data.deep_stringify_keys
29
+ has_change_and_not_force_overwrite = has_change & !force_overwrite
30
+
31
+ if has_change_and_not_force_overwrite && !yes?("This operation will overwrite the rule in the database (Rule ID: #{rule.id}). Are you sure you want to update the rule? (y/n)")
32
+ return
33
+ end
34
+
35
+ # update the rule
36
+ rule.model.save
37
+ rescue ActiveRecord::RecordNotFound
38
+ # create a new rule
39
+ rule.model.save
40
+ end
41
+
42
+ with_error_notification do
43
+ alert = rule.analyzer.run
44
+ if alert
45
+ data = Mihari::Entities::Alert.represent(alert)
46
+ puts JSON.pretty_generate(data.as_json)
47
+ else
48
+ Mihari.logger.info "There is no new alert created in the database"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,7 +5,7 @@ module Mihari
5
5
  module Validator
6
6
  def self.included(thor)
7
7
  thor.class_eval do
8
- desc "rule [PATH]", "Validate rule file format"
8
+ desc "validate [PATH]", "Validate a rule file"
9
9
  #
10
10
  # Validate format of a rule
11
11
  #
@@ -13,7 +13,7 @@ module Mihari
13
13
  #
14
14
  # @return [nil]
15
15
  #
16
- def rule(path)
16
+ def validate(path)
17
17
  rule = Structs::Rule.from_path_or_id(path)
18
18
 
19
19
  begin
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- ALLOWED_DATA_TYPES = ["hash", "ip", "domain", "url", "mail"].freeze
4
+ DEFAULT_DATA_TYPES = %w[hash ip domain url mail].freeze
5
5
 
6
- DEFAULT_EMITTERS = ["database", "misp", "slack", "the_hive", "webhook"].map { |name| { emitter: name } }.freeze
6
+ DEFAULT_EMITTERS = %w[database misp slack the_hive].map { |name| { emitter: name } }.freeze
7
7
 
8
- DEFAULT_ENRICHERS = ["whois", "ipinfo", "shodan", "google_public_dns"].map { |name| { enricher: name } }.freeze
8
+ DEFAULT_ENRICHERS = %w[whois ipinfo shodan google_public_dns].map { |name| { enricher: name } }.freeze
9
9
  end
@@ -17,53 +17,44 @@ def development_env?
17
17
  env == "development"
18
18
  end
19
19
 
20
- class InitialSchema < ActiveRecord::Migration[7.0]
20
+ class V5Schema < ActiveRecord::Migration[7.0]
21
21
  def change
22
- create_table :tags, if_not_exists: true do |t|
23
- t.string :name, null: false
22
+ create_table :rules, id: :string, if_not_exists: true do |t|
23
+ t.string :title, null: false
24
+ t.string :description, null: false
25
+ t.json :data, null: false
26
+ t.timestamps
24
27
  end
25
28
 
26
29
  create_table :alerts, if_not_exists: true do |t|
27
- t.string :title, null: false
28
- t.string :description, null: true
29
- t.string :source, null: false
30
30
  t.timestamps
31
+
32
+ t.belongs_to :rule, foreign_key: true, type: :string, null: false
31
33
  end
32
34
 
33
35
  create_table :artifacts, if_not_exists: true do |t|
34
36
  t.string :data, null: false
35
37
  t.string :data_type, null: false
36
- t.belongs_to :alert, foreign_key: true
38
+ t.string :source
39
+ t.json :metadata
37
40
  t.timestamps
38
- end
39
41
 
40
- create_table :taggings, if_not_exists: true do |t|
41
- t.integer :tag_id
42
- t.integer :alert_id
42
+ t.belongs_to :alert, foreign_key: true, null: false
43
43
  end
44
44
 
45
- add_index :taggings, :tag_id, if_not_exists: true
46
- add_index :taggings, [:tag_id, :alert_id], unique: true, if_not_exists: true
47
- end
48
- end
49
-
50
- class AddeSourceToArtifactSchema < ActiveRecord::Migration[7.0]
51
- def change
52
- add_column :artifacts, :source, :string, if_not_exists: true
53
- end
54
- end
55
-
56
- class EnrichmentsSchema < ActiveRecord::Migration[7.0]
57
- def change
58
45
  create_table :autonomous_systems, if_not_exists: true do |t|
59
46
  t.integer :asn, null: false
60
- t.belongs_to :artifact, foreign_key: true
47
+ t.datetime :created_at
48
+
49
+ t.belongs_to :artifact, foreign_key: true, null: false
61
50
  end
62
51
 
63
52
  create_table :geolocations, if_not_exists: true do |t|
64
53
  t.string :country, null: false
65
54
  t.string :country_code, null: false
66
- t.belongs_to :artifact, foreign_key: true
55
+ t.datetime :created_at
56
+
57
+ t.belongs_to :artifact, foreign_key: true, null: false
67
58
  end
68
59
 
69
60
  create_table :whois_records, if_not_exists: true do |t|
@@ -73,73 +64,59 @@ class EnrichmentsSchema < ActiveRecord::Migration[7.0]
73
64
  t.date :expires_on
74
65
  t.json :registrar
75
66
  t.json :contacts
76
- t.belongs_to :artifact, foreign_key: true
67
+ t.datetime :created_at
68
+
69
+ t.belongs_to :artifact, foreign_key: true, null: false
77
70
  end
78
71
 
79
72
  create_table :dns_records, if_not_exists: true do |t|
80
73
  t.string :resource, null: false
81
74
  t.string :value, null: false
82
- t.belongs_to :artifact, foreign_key: true
75
+ t.datetime :created_at
76
+
77
+ t.belongs_to :artifact, foreign_key: true, null: false
83
78
  end
84
79
 
85
80
  create_table :reverse_dns_names, if_not_exists: true do |t|
86
81
  t.string :name, null: false
87
- t.belongs_to :artifact, foreign_key: true
88
- end
89
- end
90
- end
82
+ t.datetime :created_at
91
83
 
92
- class EnrichmentCreatedAtSchema < ActiveRecord::Migration[7.0]
93
- def change
94
- # Add created_at column because now it is able to enrich an atrifact after the creation
95
- add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
96
- add_column :geolocations, :created_at, :datetime, if_not_exists: true
97
- add_column :whois_records, :created_at, :datetime, if_not_exists: true
98
- add_column :dns_records, :created_at, :datetime, if_not_exists: true
99
- add_column :reverse_dns_names, :created_at, :datetime, if_not_exists: true
100
- end
101
- end
102
-
103
- class RuleSchema < ActiveRecord::Migration[7.0]
104
- def change
105
- create_table :rules, id: :string, if_not_exists: true do |t|
106
- t.string :title, null: false
107
- t.string :description, null: false
108
- t.json :data, null: false
109
- t.timestamps
84
+ t.belongs_to :artifact, foreign_key: true, null: false
110
85
  end
111
- end
112
- end
113
-
114
- class AddeMetadataToArtifactSchema < ActiveRecord::Migration[7.0]
115
- def change
116
- add_column :artifacts, :metadata, :json, if_not_exists: true
117
- end
118
- end
119
86
 
120
- class AddYAMLToRulesSchema < ActiveRecord::Migration[7.0]
121
- def change
122
- add_column :rules, :yaml, :text, if_not_exists: true
123
- end
124
- end
125
-
126
- class EnrichmentsV45Schema < ActiveRecord::Migration[7.0]
127
- def change
128
87
  create_table :cpes, if_not_exists: true do |t|
129
88
  t.string :cpe, null: false
130
- t.belongs_to :artifact, foreign_key: true
89
+ t.datetime :created_at
90
+
91
+ t.belongs_to :artifact, foreign_key: true, null: false
131
92
  end
132
93
 
133
94
  create_table :ports, if_not_exists: true do |t|
134
95
  t.integer :port, null: false
135
- t.belongs_to :artifact, foreign_key: true
96
+ t.datetime :created_at
97
+
98
+ t.belongs_to :artifact, foreign_key: true, null: false
99
+ end
100
+
101
+ create_table :tags, if_not_exists: true do |t|
102
+ t.string :name, null: false
103
+ t.datetime :created_at
136
104
  end
105
+
106
+ create_table :taggings, if_not_exists: true do |t|
107
+ t.integer :tag_id
108
+ t.integer :alert_id
109
+ t.datetime :created_at
110
+ end
111
+
112
+ add_index :taggings, :tag_id, if_not_exists: true
113
+ add_index :taggings, %i[tag_id alert_id], unique: true, if_not_exists: true
137
114
  end
138
115
  end
139
116
 
140
117
  def adapter
141
- return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
142
- return "mysql2" if Mihari.config.database.start_with?("mysql2://")
118
+ return "postgresql" if %w[postgresql postgres].include?(Mihari.config.database_url.scheme)
119
+ return "mysql2" if Mihari.config.database_url.scheme == "mysql2"
143
120
 
144
121
  "sqlite3"
145
122
  end
@@ -157,19 +134,7 @@ module Mihari
157
134
  def migrate(direction)
158
135
  ActiveRecord::Migration.verbose = false
159
136
 
160
- [
161
- InitialSchema,
162
- AddeSourceToArtifactSchema,
163
- EnrichmentsSchema,
164
- EnrichmentCreatedAtSchema,
165
- # v4.0
166
- RuleSchema,
167
- AddeMetadataToArtifactSchema,
168
- # v4.4
169
- AddYAMLToRulesSchema,
170
- # v4.5
171
- EnrichmentsV45Schema
172
- ].each { |schema| schema.migrate direction }
137
+ [V5Schema].each { |schema| schema.migrate direction }
173
138
  end
174
139
  memoize :migrate unless test_env?
175
140
 
@@ -181,19 +146,19 @@ module Mihari
181
146
 
182
147
  case adapter
183
148
  when "postgresql", "mysql2"
184
- ActiveRecord::Base.establish_connection(Mihari.config.database)
149
+ ActiveRecord::Base.establish_connection(Mihari.config.database_url.to_s)
185
150
  else
186
151
  ActiveRecord::Base.establish_connection(
187
152
  adapter: adapter,
188
- database: Mihari.config.database
153
+ database: Mihari.config.database_url.path[1..]
189
154
  )
190
155
  end
191
156
 
192
157
  ActiveRecord::Base.logger = Logger.new($stdout) if development_env?
193
158
 
194
159
  migrate :up
195
- rescue StandardError
196
- # Do nothing
160
+ rescue StandardError => e
161
+ Mihari.logger.error e
197
162
  end
198
163
 
199
164
  #