mihari 4.3.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
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