mihari 4.12.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/Steepfile +0 -1
  3. data/lib/mihari/analyzers/base.rb +12 -28
  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/{search.rb → searcher.rb} +9 -20
  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 +96 -83
  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/emitters/http.rb +0 -127
  56. data/lib/mihari/entities/source.rb +0 -9
  57. data/lib/mihari/status.rb +0 -55
  58. data/lib/mihari/web/endpoints/sources.rb +0 -19
  59. data/lib/mihari/web/public/assets/fa-brands-400-c61287c2.woff2 +0 -0
  60. data/lib/mihari/web/public/assets/fa-regular-400-5da313b0.woff2 +0 -0
  61. data/lib/mihari/web/public/assets/fa-regular-400-d7b19fe2.ttf +0 -0
  62. data/lib/mihari/web/public/assets/fa-solid-900-8f06540f.woff2 +0 -0
  63. data/lib/mihari/web/public/assets/fa-solid-900-e4f6a7e9.ttf +0 -0
  64. data/lib/mihari/web/public/assets/fa-v4compatibility-2ddb3b41.ttf +0 -0
  65. data/lib/mihari/web/public/assets/fa-v4compatibility-f46715c9.woff2 +0 -0
  66. data/lib/mihari/web/public/assets/index-a7fe697b.js +0 -63
  67. data/sig/lib/mihari/analyzers/base.rbs +0 -90
  68. data/sig/lib/mihari/analyzers/binaryedge.rbs +0 -26
  69. data/sig/lib/mihari/analyzers/censys.rbs +0 -41
  70. data/sig/lib/mihari/analyzers/circl.rbs +0 -31
  71. data/sig/lib/mihari/analyzers/crtsh.rbs +0 -17
  72. data/sig/lib/mihari/analyzers/dnpedia.rbs +0 -15
  73. data/sig/lib/mihari/analyzers/dnstwister.rbs +0 -25
  74. data/sig/lib/mihari/analyzers/feed.rbs +0 -20
  75. data/sig/lib/mihari/analyzers/onyphe.rbs +0 -34
  76. data/sig/lib/mihari/analyzers/otx.rbs +0 -33
  77. data/sig/lib/mihari/analyzers/passivetotal.rbs +0 -35
  78. data/sig/lib/mihari/analyzers/pulsedive.rbs +0 -27
  79. data/sig/lib/mihari/analyzers/rule.rbs +0 -68
  80. data/sig/lib/mihari/analyzers/securitytrails.rbs +0 -33
  81. data/sig/lib/mihari/analyzers/shodan.rbs +0 -36
  82. data/sig/lib/mihari/analyzers/urlscan.rbs +0 -31
  83. data/sig/lib/mihari/analyzers/virustotal.rbs +0 -31
  84. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +0 -33
  85. data/sig/lib/mihari/analyzers/zoomeye.rbs +0 -35
  86. data/sig/lib/mihari/cli/base.rbs +0 -9
  87. data/sig/lib/mihari/cli/init.rbs +0 -7
  88. data/sig/lib/mihari/cli/main.rbs +0 -9
  89. data/sig/lib/mihari/cli/validator.rbs +0 -7
  90. data/sig/lib/mihari/commands/init.rbs +0 -9
  91. data/sig/lib/mihari/commands/json.rbs +0 -7
  92. data/sig/lib/mihari/commands/search.rbs +0 -35
  93. data/sig/lib/mihari/commands/validator.rbs +0 -9
  94. data/sig/lib/mihari/commands/web.rbs +0 -7
  95. data/sig/lib/mihari/constants.rbs +0 -5
  96. data/sig/lib/mihari/database.rbs +0 -25
  97. data/sig/lib/mihari/emitters/base.rbs +0 -18
  98. data/sig/lib/mihari/emitters/database.rbs +0 -9
  99. data/sig/lib/mihari/emitters/http.rbs +0 -35
  100. data/sig/lib/mihari/emitters/misp.rbs +0 -34
  101. data/sig/lib/mihari/emitters/slack.rbs +0 -73
  102. data/sig/lib/mihari/emitters/stdout.rbs +0 -9
  103. data/sig/lib/mihari/emitters/the_hive.rbs +0 -32
  104. data/sig/lib/mihari/emitters/webhook.rbs +0 -20
  105. data/sig/lib/mihari/enrichers/base.rbs +0 -12
  106. data/sig/lib/mihari/enrichers/google_public_dns.rbs +0 -18
  107. data/sig/lib/mihari/enrichers/ipinfo.rbs +0 -16
  108. data/sig/lib/mihari/errors.rbs +0 -10
  109. data/sig/lib/mihari/feed/parser.rbs +0 -11
  110. data/sig/lib/mihari/feed/reader.rbs +0 -56
  111. data/sig/lib/mihari/http.rbs +0 -64
  112. data/sig/lib/mihari/mixins/autonomous_system.rbs +0 -14
  113. data/sig/lib/mihari/mixins/configurable.rbs +0 -30
  114. data/sig/lib/mihari/mixins/configuration.rbs +0 -45
  115. data/sig/lib/mihari/mixins/disallowed_data_value.rbs +0 -23
  116. data/sig/lib/mihari/mixins/error_notification.rbs +0 -12
  117. data/sig/lib/mihari/mixins/hash.rbs +0 -14
  118. data/sig/lib/mihari/mixins/refang.rbs +0 -14
  119. data/sig/lib/mihari/mixins/retriable.rbs +0 -15
  120. data/sig/lib/mihari/models/alert.rbs +0 -18
  121. data/sig/lib/mihari/models/artifact.rbs +0 -69
  122. data/sig/lib/mihari/models/autonomous_system.rbs +0 -14
  123. data/sig/lib/mihari/models/cpe.rbs +0 -7
  124. data/sig/lib/mihari/models/dns.rbs +0 -19
  125. data/sig/lib/mihari/models/geolocation.rbs +0 -15
  126. data/sig/lib/mihari/models/port.rbs +0 -7
  127. data/sig/lib/mihari/models/reverse_dns.rbs +0 -14
  128. data/sig/lib/mihari/models/rule.rbs +0 -17
  129. data/sig/lib/mihari/models/tag.rbs +0 -5
  130. data/sig/lib/mihari/models/tagging.rbs +0 -4
  131. data/sig/lib/mihari/models/whois.rbs +0 -66
  132. data/sig/lib/mihari/status.rbs +0 -25
  133. data/sig/lib/mihari/structs/censys.rbs +0 -58
  134. data/sig/lib/mihari/structs/filters.rbs +0 -40
  135. data/sig/lib/mihari/structs/google_public_dns.rbs +0 -21
  136. data/sig/lib/mihari/structs/greynoise.rbs +0 -30
  137. data/sig/lib/mihari/structs/ipinfo.rbs +0 -17
  138. data/sig/lib/mihari/structs/onyphe.rbs +0 -25
  139. data/sig/lib/mihari/structs/rule.rbs +0 -57
  140. data/sig/lib/mihari/structs/shodan.rbs +0 -30
  141. data/sig/lib/mihari/structs/urlscan.rbs +0 -28
  142. data/sig/lib/mihari/structs/virustotal_intelligence.rbs +0 -33
  143. data/sig/lib/mihari/type_checker.rbs +0 -48
  144. data/sig/lib/mihari/types.rbs +0 -23
  145. data/sig/lib/mihari/version.rbs +0 -3
  146. data/sig/lib/mihari/web/app.rbs +0 -5
  147. 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: 41784cbe3811a8f5f2a6a4de663ceaa03e2635c329c455b523e5fc55fda0c9e7
4
+ data.tar.gz: '052987fcb8805a8f71a0716d5e0970b0c4c7451059dded4b5149679ccda6c999'
5
5
  SHA512:
6
- metadata.gz: edde8ae3fb93a7e3719788c6727782f7f9a9e2ae53eaa9d804342e545e0ca5e9ec6bb96d633f9c26a33f98e4addaf8693b6da37de8930f8770847a531665baa0
7
- data.tar.gz: afda1bef59be058cbb5972d771bf7615820cc4588cae6cc53c2f2d39333e02edce9a3165dee30caf0996195ab09565392316d116040ce5cc93dbdac28b04c239
6
+ metadata.gz: f16447e83adb4630baf9587eecea0dbb6f580de23894d70fa2c1729483faa6dc1c86fe5de2bd782100f8cba89e2a50e7ce2f0ab675de822fe5fb767fb2c6f45f
7
+ data.tar.gz: af70fcc510ed4fd6a434481a8372c56d20a54b605ee0b49425a1315531d2261db6df9588f1961ba34796728ff40f0ce188ad58e6c542f4c3644a8c8251b37eb5
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,20 @@ 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
12
  include Mixins::Database
11
13
  include Mixins::Retriable
12
14
 
13
- attr_accessor :ignore_old_artifacts, :ignore_threshold
15
+ # @return [Mihari::Structs::Rule, nil]
16
+ attr_reader :rule
14
17
 
15
18
  def initialize(*args, **kwargs)
16
- super
19
+ super(*args, **kwargs)
17
20
 
18
- @ignore_old_artifacts = false
19
- @ignore_threshold = 0
21
+ @base_time = Time.now.utc
20
22
  end
21
23
 
22
24
  # @return [Array<String>, Array<Mihari::Artifact>]
@@ -24,26 +26,11 @@ module Mihari
24
26
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
25
27
  end
26
28
 
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
29
  # @return [String]
38
30
  def source
39
31
  self.class.to_s.split("::").last.to_s
40
32
  end
41
33
 
42
- # @return [Array<String>]
43
- def tags
44
- []
45
- end
46
-
47
34
  #
48
35
  # Set artifacts & run emitters in parallel
49
36
  #
@@ -77,13 +64,7 @@ module Mihari
77
64
  def run_emitter(emitter)
78
65
  return if enriched_artifacts.empty?
79
66
 
80
- alert_or_something = emitter.run(
81
- title: title,
82
- description: description,
83
- artifacts: enriched_artifacts,
84
- source: source,
85
- tags: tags
86
- )
67
+ alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
87
68
 
88
69
  Mihari.logger.info "Emission by #{emitter.class} is succedded"
89
70
 
@@ -112,7 +93,10 @@ module Mihari
112
93
  # No need to set data_type manually
113
94
  # It is set automatically in #initialize
114
95
  artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
115
- end.select(&:valid?).uniq(&:data)
96
+ end.select(&:valid?).uniq(&:data).map do |artifact|
97
+ artifact.rule_id = rule&.id
98
+ artifact
99
+ end
116
100
  end
117
101
 
118
102
  private
@@ -124,7 +108,7 @@ module Mihari
124
108
  #
125
109
  def unique_artifacts
126
110
  @unique_artifacts ||= normalized_artifacts.select do |artifact|
127
- artifact.unique?(ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold)
111
+ artifact.unique?(base_time: @base_time, artifact_lifetime: rule&.artifact_lifetime)
128
112
  end
129
113
  end
130
114
 
@@ -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
@@ -2,15 +2,15 @@
2
2
 
3
3
  module Mihari
4
4
  module Commands
5
- module Search
5
+ module Searcher
6
6
  include Mixins::Database
7
7
  include Mixins::ErrorNotification
8
8
 
9
9
  def self.included(thor)
10
10
  thor.class_eval do
11
- desc "search [RULE]", "Search by a rule"
11
+ desc "search [PATH]", "Search by a rule"
12
12
  method_option :yes, type: :boolean, aliases: "-y", desc: "yes to overwrite the rule in the database"
13
- def search_by_rule(path_or_id)
13
+ def search(path_or_id)
14
14
  rule = Structs::Rule.from_path_or_id path_or_id
15
15
 
16
16
  # validate
@@ -21,38 +21,27 @@ module Mihari
21
21
  end
22
22
 
23
23
  # check update
24
- id = rule.id
25
24
  yes = options["yes"] || false
26
25
  unless yes
27
26
  with_db_connection do
28
- rule_ = Mihari::Rule.find(id)
29
- next if rule.yaml == rule_.yaml
30
- unless yes?("This operation will overwrite the rule in the database (Rule ID: #{id}). Are you sure you want to update the rule? (yes/no)")
27
+ next if Mihari::Rule.find(rule.id).data == rule.data.deep_stringify_keys
28
+ unless 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)")
31
29
  return
32
30
  end
33
31
  rescue ActiveRecord::RecordNotFound
34
32
  next
35
33
  end
36
34
  end
37
-
38
- analyzer = rule.to_analyzer
35
+ # update rule model
36
+ rule.model.save
39
37
 
40
38
  with_error_notification do
41
- alert = analyzer.run
42
-
39
+ alert = rule.analyzer.run
43
40
  if alert
44
41
  data = Mihari::Entities::Alert.represent(alert)
45
42
  puts JSON.pretty_generate(data.as_json)
46
43
  else
47
- Mihari.logger.info "No new alert created in the database"
48
- end
49
-
50
- # record a rule
51
- with_db_connection do
52
- model = rule.to_model
53
- model.save
54
- rescue ActiveRecord::RecordNotUnique
55
- nil
44
+ Mihari.logger.info "There is no new alert created in the database"
56
45
  end
57
46
  end
58
47
  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
  #
@@ -7,23 +7,32 @@ module Mihari
7
7
  true
8
8
  end
9
9
 
10
- def emit(title:, description:, artifacts:, source:, tags: [])
10
+ #
11
+ # Create an alert
12
+ #
13
+ # @param [Arra<Mihari::Artifact>] artifacts
14
+ # @param [Mihari::Structs::Rule] rule
15
+ #
16
+ # @return [Mihari::Alert]
17
+ #
18
+ def emit(artifacts:, rule:)
11
19
  return if artifacts.empty?
12
20
 
13
- tags = tags.filter_map { |name| Tag.find_or_create_by(name: name) }.uniq
21
+ tags = rule.tags.filter_map { |name| Tag.find_or_create_by(name: name) }.uniq
14
22
  taggings = tags.map { |tag| Tagging.new(tag_id: tag.id) }
15
23
 
16
24
  alert = Alert.new(
17
- title: title,
18
- description: description,
19
25
  artifacts: artifacts,
20
- source: source,
21
- taggings: taggings
26
+ taggings: taggings,
27
+ rule_id: rule.id
22
28
  )
23
-
24
29
  alert.save
25
30
  alert
26
31
  end
32
+
33
+ def configuration_keys
34
+ %w[database_url]
35
+ end
27
36
  end
28
37
  end
29
38
  end