mihari 4.3.0 → 4.4.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/analyzers/binaryedge.rb +10 -1
  3. data/lib/mihari/analyzers/censys.rb +26 -1
  4. data/lib/mihari/analyzers/circl.rb +23 -1
  5. data/lib/mihari/analyzers/greynoise.rb +10 -1
  6. data/lib/mihari/analyzers/onyphe.rb +10 -1
  7. data/lib/mihari/analyzers/otx.rb +8 -2
  8. data/lib/mihari/analyzers/passivetotal.rb +25 -3
  9. data/lib/mihari/analyzers/pulsedive.rb +7 -1
  10. data/lib/mihari/analyzers/securitytrails.rb +7 -1
  11. data/lib/mihari/analyzers/shodan.rb +10 -1
  12. data/lib/mihari/analyzers/spyse.rb +10 -1
  13. data/lib/mihari/analyzers/urlscan.rb +6 -1
  14. data/lib/mihari/analyzers/virustotal.rb +7 -2
  15. data/lib/mihari/analyzers/virustotal_intelligence.rb +6 -1
  16. data/lib/mihari/analyzers/zoomeye.rb +16 -2
  17. data/lib/mihari/cli/main.rb +2 -0
  18. data/lib/mihari/commands/search.rb +14 -0
  19. data/lib/mihari/commands/version.rb +18 -0
  20. data/lib/mihari/database.rb +10 -2
  21. data/lib/mihari/emitters/misp.rb +14 -8
  22. data/lib/mihari/emitters/slack.rb +20 -28
  23. data/lib/mihari/emitters/the_hive.rb +18 -6
  24. data/lib/mihari/entities/rule.rb +1 -12
  25. data/lib/mihari/errors.rb +2 -0
  26. data/lib/mihari/mixins/configurable.rb +12 -1
  27. data/lib/mihari/mixins/rule.rb +16 -19
  28. data/lib/mihari/models/artifact.rb +7 -2
  29. data/lib/mihari/models/rule.rb +12 -3
  30. data/lib/mihari/schemas/analyzer.rb +89 -10
  31. data/lib/mihari/schemas/emitter.rb +35 -0
  32. data/lib/mihari/schemas/rule.rb +5 -62
  33. data/lib/mihari/structs/rule.rb +33 -5
  34. data/lib/mihari/types.rb +0 -25
  35. data/lib/mihari/version.rb +1 -1
  36. data/lib/mihari/web/endpoints/rules.rb +20 -3
  37. data/lib/mihari/web/public/index.html +1 -1
  38. data/lib/mihari/web/public/redoc-static.html +11 -11
  39. data/lib/mihari/web/public/static/css/app.de5845d8.css +1 -0
  40. data/lib/mihari/web/public/static/css/chunk-vendors.da2a7bfc.css +7 -0
  41. data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js +2 -0
  42. data/lib/mihari/web/public/static/js/app-legacy.f550d6ae.js.map +1 -0
  43. data/lib/mihari/web/public/static/js/app.40749592.js +2 -0
  44. data/lib/mihari/web/public/static/js/app.40749592.js.map +1 -0
  45. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js +25 -0
  46. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.d6b76c57.js.map +1 -0
  47. data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js +31 -0
  48. data/lib/mihari/web/public/static/js/chunk-vendors.3bdbaffb.js.map +1 -0
  49. data/mihari.gemspec +2 -2
  50. data/sig/lib/mihari/analyzers/binaryedge.rbs +2 -0
  51. data/sig/lib/mihari/analyzers/censys.rbs +4 -0
  52. data/sig/lib/mihari/analyzers/circl.rbs +5 -1
  53. data/sig/lib/mihari/analyzers/onyphe.rbs +2 -0
  54. data/sig/lib/mihari/analyzers/otx.rbs +3 -1
  55. data/sig/lib/mihari/analyzers/passivetotal.rbs +5 -1
  56. data/sig/lib/mihari/analyzers/pulsedive.rbs +3 -1
  57. data/sig/lib/mihari/analyzers/securitytrails.rbs +3 -1
  58. data/sig/lib/mihari/analyzers/shodan.rbs +2 -0
  59. data/sig/lib/mihari/analyzers/spyse.rbs +3 -1
  60. data/sig/lib/mihari/analyzers/urlscan.rbs +2 -0
  61. data/sig/lib/mihari/analyzers/virustotal.rbs +3 -1
  62. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +2 -0
  63. data/sig/lib/mihari/analyzers/zoomeye.rbs +3 -1
  64. data/sig/lib/mihari/emitters/misp.rbs +6 -0
  65. data/sig/lib/mihari/emitters/slack.rbs +8 -20
  66. data/sig/lib/mihari/emitters/the_hive.rbs +4 -0
  67. data/sig/lib/mihari/mixins/configurable.rbs +4 -0
  68. data/sig/lib/mihari/mixins/rule.rbs +2 -0
  69. data/sig/lib/mihari/models/rule.rbs +3 -0
  70. data/sig/lib/mihari/structs/rule.rbs +5 -1
  71. metadata +18 -6
@@ -109,48 +109,46 @@ module Mihari
109
109
  end
110
110
 
111
111
  class Slack < Base
112
- SLACK_WEBHOOK_URL_KEY = "SLACK_WEBHOOK_URL"
113
- SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
114
- DEFAULT_USERNAME = "mihari"
112
+ DEFAULT_CHANNEL = "#general"
113
+ DEFAULT_USERNAME = "Mihari"
114
+
115
+ # @return [String, nil]
116
+ attr_reader :webhook_url
115
117
 
116
- #
117
- # Slack channel to post
118
- #
119
118
  # @return [String]
120
- #
121
- def slack_channel
122
- Mihari.config.slack_channel || "#general"
123
- end
119
+ attr_reader :channel
124
120
 
125
- #
126
- # Slack webhook URL
127
- #
128
121
  # @return [String]
129
- #
130
- def slack_webhook_url
131
- Mihari.config.slack_webhook_url
122
+ attr_reader :username
123
+
124
+ def initialize(*args, **kwargs)
125
+ super(*args, **kwargs)
126
+
127
+ @webhook_url = kwargs[:webhook_url] || Mihari.config.slack_webhook_url
128
+ @channel = kwargs[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
129
+ @username = DEFAULT_USERNAME
132
130
  end
133
131
 
134
132
  #
135
- # Check Slack webhook URL is set
133
+ # Check webhook URL is set
136
134
  #
137
135
  # @return [Boolean]
138
136
  #
139
- def slack_webhook_url?
140
- !Mihari.config.slack_webhook_url.nil?
137
+ def webhook_url?
138
+ !webhook_url.nil?
141
139
  end
142
140
 
143
141
  #
144
- # Check Slack webhook URL is set. Alias of #slack_webhook_url?.
142
+ # Check webhook URL is set. Alias of #webhook_url?
145
143
  #
146
144
  # @return [Boolean]
147
145
  #
148
146
  def valid?
149
- slack_webhook_url?
147
+ webhook_url?
150
148
  end
151
149
 
152
150
  def notifier
153
- @notifier ||= ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel, username: DEFAULT_USERNAME)
151
+ @notifier ||= ::Slack::Notifier.new(webhook_url, channel: channel, username: username)
154
152
  end
155
153
 
156
154
  #
@@ -193,12 +191,6 @@ module Mihari
193
191
 
194
192
  notifier.post(text: text, attachments: attachments, mrkdwn: true)
195
193
  end
196
-
197
- private
198
-
199
- def configuration_keys
200
- %w[slack_webhook_url]
201
- end
202
194
  end
203
195
  end
204
196
  end
@@ -5,6 +5,19 @@ require "hachi"
5
5
  module Mihari
6
6
  module Emitters
7
7
  class TheHive < Base
8
+ # @return [String, nil]
9
+ attr_reader :api_endpoint
10
+
11
+ # @return [String, nil]
12
+ attr_reader :api_key
13
+
14
+ def initialize(*args, **kwargs)
15
+ super(*args, **kwargs)
16
+
17
+ @api_endpoint = kwargs[:api_endpoint] || Mihari.config.thehive_api_endpoint
18
+ @api_key = kwargs[:api_key] || Mihari.config.thehive_api_key
19
+ end
20
+
8
21
  # @return [Boolean]
9
22
  def valid?
10
23
  api_endpont? && api_key? && ping?
@@ -30,7 +43,7 @@ module Mihari
30
43
  end
31
44
 
32
45
  def api
33
- @api ||= Hachi::API.new(api_endpoint: Mihari.config.thehive_api_endpoint, api_key: Mihari.config.thehive_api_key)
46
+ @api ||= Hachi::API.new(api_endpoint: api_endpoint, api_key: api_key)
34
47
  end
35
48
 
36
49
  #
@@ -39,16 +52,16 @@ module Mihari
39
52
  # @return [Boolean]
40
53
  #
41
54
  def api_endpont?
42
- !Mihari.config.thehive_api_endpoint.nil?
55
+ !api_endpoint.nil?
43
56
  end
44
57
 
45
58
  #
46
59
  # Check whether an API key is set or not
47
60
  #
48
61
  # @return [Boolean]
49
- # ]
62
+ #
50
63
  def api_key?
51
- !Mihari.config.thehive_api_key.nil?
64
+ !api_key.nil?
52
65
  end
53
66
 
54
67
  #
@@ -57,8 +70,7 @@ module Mihari
57
70
  # @return [Boolean]
58
71
  #
59
72
  def ping?
60
- base_url = Mihari.config.thehive_api_endpoint
61
- base_url = base_url.end_with?("/") ? base_url[0..-2] : base_url
73
+ base_url = api_endpoint.end_with?("/") ? api_endpoint[0..-2] : api_endpoint
62
74
  url = "#{base_url}/index.html"
63
75
 
64
76
  http = Net::Ping::HTTP.new(url)
@@ -14,20 +14,9 @@ module Mihari
14
14
 
15
15
  class Rule < Grape::Entity
16
16
  expose :id, documentation: { type: String, required: true }
17
-
18
17
  expose :yaml, documentation: { type: String, required: true }
19
-
20
- expose :title, documentation: { type: String, required: true }
21
- expose :description, documentation: { type: String, required: true }
22
- expose :queries, using: Entities::Query, documentation: { type: Entities::Query, is_array: true, required: true }
23
- expose :emitters, using: Entities::Emitter, documentation: { type: Entities::Emitter, is_array: true, required: false }
24
- expose :tags, documentation: { type: String, is_array: true }
25
- expose :allowed_data_types, documentation: { type: String, is_array: true }, as: :allowedDtaTypes
26
- expose :disallowed_data_values, documentation: { type: String, is_array: true }, as: :disallowedDataValues
27
- expose :ignore_old_artifacts, documentation: { type: "boolean", required: true }, as: :ignoreOldArtifacts
28
- expose :ignore_threshold, documentation: { type: Integer, required: true }, as: :ignoreThreshold
29
-
30
18
  expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
19
+ expose :updated_at, documentation: { type: DateTime, required: true }, as: :updatedAt
31
20
  end
32
21
 
33
22
  class RulesWithPagination < Grape::Entity
data/lib/mihari/errors.rb CHANGED
@@ -5,6 +5,8 @@ module Mihari
5
5
 
6
6
  class InvalidInputError < Error; end
7
7
 
8
+ class InvalidArtifactFormatError < Error; end
9
+
8
10
  class RetryableError < Error; end
9
11
 
10
12
  class FileNotFoundError < Error; end
@@ -9,7 +9,9 @@ module Mihari
9
9
  # @return [Boolean]
10
10
  #
11
11
  def configured?
12
- configuration_keys.all? { |key| Mihari.config.send(key) }
12
+ return true if configuration_keys.empty?
13
+
14
+ configuration_keys.all? { |key| Mihari.config.send(key) } || api_key?
13
15
  end
14
16
 
15
17
  #
@@ -33,6 +35,15 @@ module Mihari
33
35
  def configuration_keys
34
36
  []
35
37
  end
38
+
39
+ private
40
+
41
+ def api_key?
42
+ value = method(:api_key).call
43
+ !value.nil?
44
+ rescue NameError
45
+ true
46
+ end
36
47
  end
37
48
  end
38
49
  end
@@ -9,6 +9,10 @@ module Mihari
9
9
  module Rule
10
10
  include Mixins::Database
11
11
 
12
+ def load_erb_yaml(yaml)
13
+ YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date], symbolize_names: true)
14
+ end
15
+
12
16
  #
13
17
  # Load rule into hash
14
18
  #
@@ -17,24 +21,24 @@ module Mihari
17
21
  # @return [Mihari::Structs::Rule::Rule]
18
22
  #
19
23
  def load_rule(path_or_id)
20
- data = nil
24
+ yaml = nil
21
25
 
22
- data = load_data_from_file(path_or_id) if File.exist?(path_or_id)
23
- data = load_data_from_db(path_or_id) if data.nil?
26
+ yaml = load_yaml_from_file(path_or_id) if File.exist?(path_or_id)
27
+ yaml = load_yaml_from_db(path_or_id) if yaml.nil?
24
28
 
25
- Structs::Rule::Rule.new(data)
29
+ Structs::Rule::Rule.from_yaml yaml
26
30
  end
27
31
 
28
- def load_data_from_file(path)
29
- return YAML.safe_load(File.read(path), permitted_classes: [Date], symbolize_names: true) if Pathname(path).exist?
32
+ def load_yaml_from_file(path)
33
+ return nil unless Pathname(path).exist?
30
34
 
31
- YAML.safe_load(path, symbolize_names: true)
35
+ File.read path
32
36
  end
33
37
 
34
- def load_data_from_db(id)
38
+ def load_yaml_from_db(id)
35
39
  with_db_connection do
36
40
  rule = Mihari::Rule.find(id)
37
- rule.data
41
+ rule.yaml || rule.symbolized_data.to_yaml
38
42
  rescue ActiveRecord::RecordNotFound
39
43
  raise ArgumentError, "ID:#{id} is not found in the database"
40
44
  end
@@ -58,16 +62,9 @@ module Mihari
58
62
  # @return [String] A template for rule
59
63
  #
60
64
  def rule_template
61
- # Use ERB to fill created_on and updated_on with Date.today
62
- data = File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
63
- template = ERB.new(data)
64
- data = template.result
65
-
66
- # validate the template of rule for just in case
67
- hashed_data = YAML.safe_load(data, permitted_classes: [Date], symbolize_names: true)
68
- Structs::Rule::Rule.new(hashed_data)
69
-
70
- data
65
+ yaml = File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
66
+ Structs::Rule::Rule.from_yaml yaml
67
+ yaml
71
68
  end
72
69
 
73
70
  #
@@ -25,8 +25,13 @@ module Mihari
25
25
 
26
26
  attr_accessor :tags
27
27
 
28
- def initialize(attributes)
29
- super
28
+ def initialize(*args, **kwargs)
29
+ attrs = args.first || kwargs
30
+ data_ = attrs[:data]
31
+
32
+ raise InvalidArtifactFormatError if data_.is_a?(Array) || data_.is_a?(Hash)
33
+
34
+ super(*args, **kwargs)
30
35
 
31
36
  self.data_type = TypeChecker.type(data)
32
37
  self.tags = []
@@ -1,18 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "yaml"
4
+
3
5
  module Mihari
4
6
  class Rule < ActiveRecord::Base
5
7
  has_many :alerts, foreign_key: :source
6
8
 
9
+ def symbolized_data
10
+ @symbolized_data ||= data.deep_symbolize_keys
11
+ end
12
+
7
13
  #
8
14
  # Returns a hash representative
9
15
  #
10
16
  # @return [Hash]
11
17
  #
12
18
  def to_h
13
- symbolized_data = data.deep_symbolize_keys
14
- h = { id: id, created_at: created_at, yaml: data.to_yaml }
15
- h.merge symbolized_data
19
+ {
20
+ id: id,
21
+ yaml: yaml || data.to_yaml,
22
+ created_at: created_at,
23
+ updated_at: updated_at
24
+ }
16
25
  end
17
26
 
18
27
  class << self
@@ -2,19 +2,98 @@
2
2
 
3
3
  module Mihari
4
4
  module Schemas
5
- AnalyzerRun = Dry::Schema.Params do
6
- required(:title).value(:string)
7
- required(:description).value(:string)
8
- required(:source).value(:string)
9
- required(:artifacts).value(array[:string])
5
+ AnalyzerOptions = Dry::Schema.Params do
6
+ optional(:interval).value(:integer)
7
+ end
8
+
9
+ AnalyzerWithoutAPIKey = Dry::Schema.Params do
10
+ required(:analyzer).value(Types::String.enum("crtsh", "dnpedia", "dnstwister"))
11
+ required(:query).value(:string)
12
+ optional(:options).hash(AnalyzerOptions)
13
+ end
14
+
15
+ AnalyzerWithAPIKey = Dry::Schema.Params do
16
+ required(:analyzer).value(
17
+ Types::String.enum(
18
+ "binaryedge",
19
+ "greynoise",
20
+ "onyphe",
21
+ "otx",
22
+ "pulsedive",
23
+ "securitytrails",
24
+ "shodan",
25
+ "st",
26
+ "virustotal_intelligence",
27
+ "virustotal",
28
+ "vt_intel",
29
+ "vt"
30
+ )
31
+ )
32
+ required(:query).value(:string)
33
+ optional(:api_key).value(:string)
34
+ optional(:options).hash(AnalyzerOptions)
35
+ end
36
+
37
+ Censys = Dry::Schema.Params do
38
+ required(:analyzer).value(Types::String.enum("censys"))
39
+ required(:query).value(:string)
40
+ optional(:id).value(:string)
41
+ optional(:secret).value(:string)
42
+ optional(:options).hash(AnalyzerOptions)
43
+ end
44
+
45
+ CIRCL = Dry::Schema.Params do
46
+ required(:analyzer).value(Types::String.enum("circl"))
47
+ required(:query).value(:string)
48
+ optional(:username).value(:string)
49
+ optional(:password).value(:string)
50
+ optional(:options).hash(AnalyzerOptions)
51
+ end
52
+
53
+ PassiveTotal = Dry::Schema.Params do
54
+ required(:analyzer).value(Types::String.enum("passivetotal", "pt"))
55
+ required(:query).value(:string)
56
+ optional(:username).value(:string)
57
+ optional(:api_key).value(:string)
58
+ optional(:options).hash(AnalyzerOptions)
59
+ end
60
+
61
+ Spyse = Dry::Schema.Params do
62
+ required(:analyzer).value(Types::String.enum("spyse"))
63
+ required(:query).value(:string)
64
+ required(:type).value(Types::String.enum("ip", "domain"))
65
+ optional(:options).hash(AnalyzerOptions)
66
+ end
67
+
68
+ ZoomEye = Dry::Schema.Params do
69
+ required(:analyzer).value(Types::String.enum("zoomeye"))
70
+ required(:query).value(:string)
71
+ required(:type).value(Types::String.enum("host", "web"))
72
+ optional(:options).hash(AnalyzerOptions)
73
+ end
74
+
75
+ Crtsh = Dry::Schema.Params do
76
+ required(:analyzer).value(Types::String.enum("crtsh"))
77
+ required(:query).value(:string)
78
+ optional(:exclude_expired).value(:bool).default(true)
79
+ optional(:options).hash(AnalyzerOptions)
80
+ end
10
81
 
11
- optional(:tags).value(array[:string]).default([])
12
- optional(:ignoreOldArtifacts).value(:bool).default(false)
13
- optional(:ignoreThreshold).value(:integer).default(0)
82
+ Urlscan = Dry::Schema.Params do
83
+ required(:analyzer).value(Types::String.enum("urlscan"))
84
+ required(:query).value(:string)
85
+ optional(:options).hash(AnalyzerOptions)
14
86
  end
15
87
 
16
- class AnalyzerRunContract < Dry::Validation::Contract
17
- params(AnalyzerRun)
88
+ Feed = Dry::Schema.Params do
89
+ required(:analyzer).value(Types::String.enum("feed"))
90
+ required(:query).value(:string)
91
+ required(:selector).value(:string)
92
+ optional(:http_request_method).value(Types::HttpRequestMethods).default("GET")
93
+ optional(:http_request_headers).value(:hash).default({})
94
+ optional(:http_request_payload).value(:hash).default({})
95
+ optional(:http_request_payload_type).value(Types::HttpRequestPayloadTypes)
96
+ optional(:options).hash(AnalyzerOptions)
18
97
  end
19
98
  end
20
99
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Schemas
5
+ Emitter = Dry::Schema.Params do
6
+ required(:emitter).value(Types::EmitterTypes)
7
+ end
8
+
9
+ MISP = Dry::Schema.Params do
10
+ required(:emitter).value(Types::String.enum("misp"))
11
+ optional(:api_endpoint).value(:string)
12
+ optional(:api_key).value(:string)
13
+ end
14
+
15
+ TheHive = Dry::Schema.Params do
16
+ required(:emitter).value(Types::String.enum("the_hive"))
17
+ optional(:api_endpoint).value(:string)
18
+ optional(:api_key).value(:string)
19
+ end
20
+
21
+ Slack = Dry::Schema.Params do
22
+ required(:emitter).value(Types::String.enum("slack"))
23
+ optional(:webhook_url).value(:string)
24
+ optional(:channel).value(:string)
25
+ end
26
+
27
+ HTTP = Dry::Schema.Params do
28
+ required(:emitter).value(Types::String.enum("http"))
29
+ required(:url).value(:string)
30
+ optional(:http_request_method).value(Types::HttpRequestMethods).default("POST")
31
+ optional(:http_request_headers).value(:hash).default({})
32
+ optional(:template).value(:string)
33
+ end
34
+ end
35
+ end
@@ -1,67 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mihari/schemas/analyzer"
4
+ require "mihari/schemas/emitter"
5
+
3
6
  module Mihari
4
7
  module Schemas
5
- AnalyzerOptions = Dry::Schema.Params do
6
- optional(:interval).value(:integer)
7
- end
8
-
9
- Analyzer = Dry::Schema.Params do
10
- required(:analyzer).value(Types::AnalyzerTypes)
11
- required(:query).value(:string)
12
- optional(:options).hash(AnalyzerOptions)
13
- end
14
-
15
- Spyse = Dry::Schema.Params do
16
- required(:analyzer).value(Types::String.enum("spyse"))
17
- required(:query).value(:string)
18
- required(:type).value(Types::String.enum("ip", "domain"))
19
- optional(:options).hash(AnalyzerOptions)
20
- end
21
-
22
- ZoomEye = Dry::Schema.Params do
23
- required(:analyzer).value(Types::String.enum("zoomeye"))
24
- required(:query).value(:string)
25
- required(:type).value(Types::String.enum("host", "web"))
26
- optional(:options).hash(AnalyzerOptions)
27
- end
28
-
29
- Crtsh = Dry::Schema.Params do
30
- required(:analyzer).value(Types::String.enum("crtsh"))
31
- required(:query).value(:string)
32
- optional(:exclude_expired).value(:bool).default(true)
33
- optional(:options).hash(AnalyzerOptions)
34
- end
35
-
36
- Urlscan = Dry::Schema.Params do
37
- required(:analyzer).value(Types::String.enum("urlscan"))
38
- required(:query).value(:string)
39
- optional(:options).hash(AnalyzerOptions)
40
- end
41
-
42
- Feed = Dry::Schema.Params do
43
- required(:analyzer).value(Types::String.enum("feed"))
44
- required(:query).value(:string)
45
- required(:selector).value(:string)
46
- optional(:http_request_method).value(Types::HttpRequestMethods).default("GET")
47
- optional(:http_request_headers).value(:hash).default({})
48
- optional(:http_request_payload).value(:hash).default({})
49
- optional(:http_request_payload_type).value(Types::HttpRequestPayloadTypes)
50
- optional(:options).hash(AnalyzerOptions)
51
- end
52
-
53
- Emitter = Dry::Schema.Params do
54
- required(:emitter).value(Types::EmitterTypes)
55
- end
56
-
57
- HTTP = Dry::Schema.Params do
58
- required(:emitter).value(Types::String.enum("http"))
59
- required(:url).value(:string)
60
- optional(:http_request_method).value(Types::HttpRequestMethods).default("POST")
61
- optional(:http_request_headers).value(:hash).default({})
62
- optional(:template).value(:string)
63
- end
64
-
65
8
  Rule = Dry::Schema.Params do
66
9
  required(:title).value(:string)
67
10
  required(:description).value(:string)
@@ -73,9 +16,9 @@ module Mihari
73
16
  optional(:created_on).value(:date)
74
17
  optional(:updated_on).value(:date)
75
18
 
76
- required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh | Feed }
19
+ required(:queries).value(:array).each { AnalyzerWithoutAPIKey | AnalyzerWithAPIKey | Censys | CIRCL | PassiveTotal | Spyse | ZoomEye | Urlscan | Crtsh | Feed }
77
20
 
78
- optional(:emitters).value(:array).each { Emitter | HTTP }
21
+ optional(:emitters).value(:array).each { Emitter | MISP | TheHive | Slack | HTTP }
79
22
 
80
23
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
81
24
  optional(:disallowed_data_values).value(array[:string]).default([])
@@ -27,10 +27,19 @@ module Mihari
27
27
  end
28
28
 
29
29
  class Rule
30
- attr_reader :data, :errors
30
+ # @return [Hash]
31
+ attr_reader :data
31
32
 
32
- def initialize(data)
33
+ # @return [String]
34
+ attr_reader :yaml
35
+
36
+ # @return [Array, nil]
37
+ attr_reader :errors
38
+
39
+ def initialize(data, yaml)
33
40
  @data = data.deep_symbolize_keys
41
+ @yaml = yaml
42
+
34
43
  @errors = nil
35
44
  @no_method_error = nil
36
45
 
@@ -76,7 +85,7 @@ module Mihari
76
85
 
77
86
  raise RuleValidationError, error_messages.join("\n") if errors?
78
87
 
79
- raise RuleValidationError, "Something wrong with queries" unless @no_method_error.nil?
88
+ raise RuleValidationError, "Something wrong with queries or emitters." unless @no_method_error.nil?
80
89
  end
81
90
 
82
91
  def [](key)
@@ -109,16 +118,20 @@ module Mihari
109
118
  #
110
119
  def to_model
111
120
  rule = Mihari::Rule.find(id)
121
+
112
122
  rule.title = title
113
123
  rule.description = description
114
124
  rule.data = data
125
+ rule.yaml = yaml
126
+
115
127
  rule
116
128
  rescue ActiveRecord::RecordNotFound
117
129
  Mihari::Rule.new(
118
130
  id: id,
119
131
  title: title,
120
132
  description: description,
121
- data: data
133
+ data: data,
134
+ yaml: yaml
122
135
  )
123
136
  end
124
137
 
@@ -143,13 +156,28 @@ module Mihari
143
156
  end
144
157
 
145
158
  class << self
159
+ include Mixins::Rule
160
+
146
161
  #
147
162
  # @param [Mihari::Rule] model
148
163
  #
149
164
  # @return [Mihari::Structs::Rule::Rule]
150
165
  #
151
166
  def from_model(model)
152
- Structs::Rule::Rule.new(model.data)
167
+ data = model.data.deep_symbolize_keys
168
+ data[:id] = model.id unless data.key?(:id)
169
+
170
+ Structs::Rule::Rule.new(data, model.yaml)
171
+ end
172
+
173
+ #
174
+ # @param [String] yaml
175
+ #
176
+ # @return [Mihari::Structs::Rule::Rule]
177
+ #
178
+ def from_yaml(yaml)
179
+ data = load_erb_yaml(yaml)
180
+ Structs::Rule::Rule.new(data, yaml)
153
181
  end
154
182
  end
155
183
  end
data/lib/mihari/types.rb CHANGED
@@ -16,36 +16,11 @@ module Mihari
16
16
 
17
17
  UrlscanDataTypes = Types::String.enum("ip", "domain", "url")
18
18
 
19
- AnalyzerTypes = Types::String.enum(
20
- "binaryedge",
21
- "censys",
22
- "circl",
23
- "dnpedia",
24
- "dnstwister",
25
- "greynoise",
26
- "onyphe",
27
- "otx",
28
- "passivetotal",
29
- "pt",
30
- "pulsedive",
31
- "securitytrails",
32
- "shodan",
33
- "st",
34
- "virustotal_intelligence",
35
- "virustotal",
36
- "vt_intel",
37
- "vt"
38
- )
39
-
40
19
  HttpRequestMethods = Types::String.enum("GET", "POST")
41
20
  HttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
42
21
 
43
22
  EmitterTypes = Types::String.enum(
44
23
  "database",
45
- "http",
46
- "misp",
47
- "slack",
48
- "the_hive",
49
24
  "webhook"
50
25
  )
51
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.3.0"
4
+ VERSION = "4.4.0"
5
5
  end