mihari 6.3.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -10
  3. data/.rubocop.yml +2 -0
  4. data/Dockerfile +13 -0
  5. data/config.ru +5 -3
  6. data/docker-compose.yml +62 -0
  7. data/exe/mihari +2 -1
  8. data/lefthook.yml +8 -0
  9. data/lib/mihari/actor.rb +4 -4
  10. data/lib/mihari/analyzers/base.rb +16 -0
  11. data/lib/mihari/analyzers/binaryedge.rb +4 -2
  12. data/lib/mihari/analyzers/censys.rb +7 -5
  13. data/lib/mihari/analyzers/circl.rb +5 -3
  14. data/lib/mihari/analyzers/crtsh.rb +4 -1
  15. data/lib/mihari/analyzers/dnstwister.rb +1 -1
  16. data/lib/mihari/analyzers/feed.rb +12 -20
  17. data/lib/mihari/analyzers/fofa.rb +6 -8
  18. data/lib/mihari/analyzers/greynoise.rb +4 -2
  19. data/lib/mihari/analyzers/hunterhow.rb +4 -2
  20. data/lib/mihari/analyzers/onyphe.rb +4 -2
  21. data/lib/mihari/analyzers/otx.rb +5 -3
  22. data/lib/mihari/analyzers/passivetotal.rb +29 -12
  23. data/lib/mihari/analyzers/pulsedive.rb +5 -3
  24. data/lib/mihari/analyzers/securitytrails.rb +32 -8
  25. data/lib/mihari/analyzers/shodan.rb +4 -2
  26. data/lib/mihari/analyzers/urlscan.rb +4 -2
  27. data/lib/mihari/analyzers/virustotal.rb +5 -5
  28. data/lib/mihari/analyzers/virustotal_intelligence.rb +4 -2
  29. data/lib/mihari/analyzers/zoomeye.rb +4 -2
  30. data/lib/mihari/cli/{main.rb → application.rb} +17 -5
  31. data/lib/mihari/cli/artifact.rb +14 -0
  32. data/lib/mihari/cli/config.rb +14 -0
  33. data/lib/mihari/cli/rule.rb +1 -0
  34. data/lib/mihari/cli/tag.rb +14 -0
  35. data/lib/mihari/clients/base.rb +2 -2
  36. data/lib/mihari/clients/binaryedge.rb +2 -2
  37. data/lib/mihari/clients/crtsh.rb +2 -9
  38. data/lib/mihari/clients/fofa.rb +1 -1
  39. data/lib/mihari/clients/hunterhow.rb +1 -1
  40. data/lib/mihari/clients/mmdb.rb +28 -0
  41. data/lib/mihari/clients/passivetotal.rb +7 -20
  42. data/lib/mihari/clients/securitytrails.rb +19 -43
  43. data/lib/mihari/clients/shodan_internet_db.rb +28 -0
  44. data/lib/mihari/clients/the_hive.rb +7 -5
  45. data/lib/mihari/commands/alert.rb +53 -11
  46. data/lib/mihari/commands/artifact.rb +66 -0
  47. data/lib/mihari/commands/config.rb +23 -0
  48. data/lib/mihari/commands/database.rb +1 -1
  49. data/lib/mihari/commands/rule.rb +40 -27
  50. data/lib/mihari/commands/search.rb +10 -11
  51. data/lib/mihari/commands/sidekiq.rb +31 -0
  52. data/lib/mihari/commands/tag.rb +46 -0
  53. data/lib/mihari/commands/web.rb +6 -7
  54. data/lib/mihari/{mixins/autonomous_system.rb → concerns/autonomous_system_normalizable.rb} +5 -3
  55. data/lib/mihari/concerns/configurable.rb +72 -0
  56. data/lib/mihari/concerns/database_connectable.rb +16 -0
  57. data/lib/mihari/{mixins/unwrap_error.rb → concerns/error_unwrappable.rb} +5 -3
  58. data/lib/mihari/{mixins/falsepositive.rb → concerns/falsepositive_validatable.rb} +5 -3
  59. data/lib/mihari/{mixins/refang.rb → concerns/refangable.rb} +5 -3
  60. data/lib/mihari/{mixins → concerns}/retriable.rb +4 -2
  61. data/lib/mihari/config.rb +13 -12
  62. data/lib/mihari/database.rb +30 -42
  63. data/lib/mihari/emitters/database.rb +5 -6
  64. data/lib/mihari/emitters/misp.rb +4 -11
  65. data/lib/mihari/emitters/slack.rb +7 -5
  66. data/lib/mihari/emitters/the_hive.rb +8 -58
  67. data/lib/mihari/emitters/webhook.rb +6 -6
  68. data/lib/mihari/enrichers/google_public_dns.rb +1 -1
  69. data/lib/mihari/enrichers/mmdb.rb +28 -0
  70. data/lib/mihari/enrichers/shodan.rb +3 -5
  71. data/lib/mihari/enrichers/whois.rb +3 -3
  72. data/lib/mihari/entities/alert.rb +3 -10
  73. data/lib/mihari/entities/artifact.rb +6 -14
  74. data/lib/mihari/entities/config.rb +2 -2
  75. data/lib/mihari/entities/cpe.rb +1 -0
  76. data/lib/mihari/entities/dns.rb +1 -0
  77. data/lib/mihari/entities/geolocation.rb +1 -0
  78. data/lib/mihari/entities/ip_address.rb +1 -3
  79. data/lib/mihari/entities/messages.rb +17 -0
  80. data/lib/mihari/entities/pagination.rb +11 -0
  81. data/lib/mihari/entities/port.rb +1 -0
  82. data/lib/mihari/entities/reverse_dns.rb +1 -0
  83. data/lib/mihari/entities/rule.rb +2 -20
  84. data/lib/mihari/entities/tag.rb +2 -2
  85. data/lib/mihari/entities/whois.rb +1 -0
  86. data/lib/mihari/errors.rb +2 -4
  87. data/lib/mihari/http.rb +4 -0
  88. data/lib/mihari/models/alert.rb +21 -53
  89. data/lib/mihari/models/artifact.rb +46 -88
  90. data/lib/mihari/models/autonomous_system.rb +5 -13
  91. data/lib/mihari/models/concerns/searchable.rb +50 -0
  92. data/lib/mihari/models/cpe.rb +3 -10
  93. data/lib/mihari/models/dns.rb +2 -6
  94. data/lib/mihari/models/geolocation.rb +7 -12
  95. data/lib/mihari/models/port.rb +3 -10
  96. data/lib/mihari/models/reverse_dns.rb +3 -8
  97. data/lib/mihari/models/rule.rb +16 -57
  98. data/lib/mihari/models/tag.rb +17 -1
  99. data/lib/mihari/models/tagging.rb +1 -1
  100. data/lib/mihari/models/whois.rb +1 -4
  101. data/lib/mihari/rule.rb +35 -24
  102. data/lib/mihari/schemas/alert.rb +1 -0
  103. data/lib/mihari/schemas/analyzer.rb +2 -2
  104. data/lib/mihari/schemas/concerns/orrable.rb +24 -0
  105. data/lib/mihari/schemas/emitter.rb +1 -2
  106. data/lib/mihari/schemas/enricher.rb +3 -4
  107. data/lib/mihari/schemas/macros.rb +1 -1
  108. data/lib/mihari/schemas/options.rb +0 -2
  109. data/lib/mihari/schemas/rule.rb +1 -2
  110. data/lib/mihari/services/{rule_builder.rb → builders.rb} +1 -6
  111. data/lib/mihari/services/creators.rb +22 -0
  112. data/lib/mihari/services/destroyers.rb +41 -0
  113. data/lib/mihari/services/enrichers.rb +25 -0
  114. data/lib/mihari/services/feed.rb +107 -0
  115. data/lib/mihari/services/getters.rb +58 -0
  116. data/lib/mihari/services/initializers.rb +22 -0
  117. data/lib/mihari/services/{alert_builder.rb → proxies.rb} +10 -40
  118. data/lib/mihari/services/searchers.rb +91 -0
  119. data/lib/mihari/sidekiq/application.rb +13 -0
  120. data/lib/mihari/sidekiq/jobs.rb +36 -0
  121. data/lib/mihari/structs/censys.rb +1 -1
  122. data/lib/mihari/structs/config.rb +10 -10
  123. data/lib/mihari/structs/filters.rb +12 -130
  124. data/lib/mihari/structs/google_public_dns.rb +1 -1
  125. data/lib/mihari/structs/greynoise.rb +1 -1
  126. data/lib/mihari/structs/mmdb.rb +115 -0
  127. data/lib/mihari/structs/onyphe.rb +1 -1
  128. data/lib/mihari/structs/shodan.rb +2 -2
  129. data/lib/mihari/version.rb +1 -1
  130. data/lib/mihari/web/{app.rb → application.rb} +28 -15
  131. data/lib/mihari/web/endpoints/alerts.rb +34 -73
  132. data/lib/mihari/web/endpoints/artifacts.rb +27 -111
  133. data/lib/mihari/web/endpoints/configs.rb +3 -5
  134. data/lib/mihari/web/endpoints/ip_addresses.rb +14 -15
  135. data/lib/mihari/web/endpoints/rules.rb +58 -130
  136. data/lib/mihari/web/endpoints/tags.rb +21 -17
  137. data/lib/mihari/web/middleware/capture_exceptions.rb +25 -0
  138. data/lib/mihari/web/middleware/{connection_adapter.rb → connection.rb} +4 -2
  139. data/lib/mihari/web/public/assets/index-cQUcyII5.js +1766 -0
  140. data/lib/mihari/web/public/assets/index-dVaNxqTC.css +1 -0
  141. data/lib/mihari/web/public/index.html +2 -2
  142. data/lib/mihari/web/public/redoc-static.html +385 -385
  143. data/lib/mihari.rb +56 -28
  144. data/mihari.gemspec +12 -4
  145. data/mkdocs.yml +5 -2
  146. data/requirements.txt +1 -1
  147. metadata +164 -34
  148. data/lib/mihari/commands/mixins.rb +0 -11
  149. data/lib/mihari/enrichers/ipinfo.rb +0 -52
  150. data/lib/mihari/entities/message.rb +0 -9
  151. data/lib/mihari/feed/parser.rb +0 -38
  152. data/lib/mihari/feed/reader.rb +0 -111
  153. data/lib/mihari/mixins/configurable.rb +0 -68
  154. data/lib/mihari/schemas/mixins.rb +0 -20
  155. data/lib/mihari/services/alert_runner.rb +0 -20
  156. data/lib/mihari/structs/ipinfo.rb +0 -53
  157. data/lib/mihari/web/endpoints/exports.rb +0 -0
  158. data/lib/mihari/web/middleware/error_notification_adapter.rb +0 -35
  159. data/lib/mihari/web/public/assets/index-81613_nX.js +0 -1763
  160. data/lib/mihari/web/public/assets/index-Wv6xUrTI.css +0 -1
@@ -56,8 +56,10 @@ module Mihari
56
56
  })
57
57
  end
58
58
 
59
- def configuration_keys
60
- %w[misp_url misp_api_key]
59
+ class << self
60
+ def configuration_keys
61
+ %w[misp_url misp_api_key]
62
+ end
61
63
  end
62
64
 
63
65
  private
@@ -126,15 +128,6 @@ module Mihari
126
128
  def url?
127
129
  !url.nil? && !url.empty?
128
130
  end
129
-
130
- #
131
- # Check whether an API key is set or not
132
- #
133
- # @return [Boolean]
134
- #
135
- def api_key?
136
- !api_key.nil? && !api_key.empty?
137
- end
138
131
  end
139
132
  end
140
133
  end
@@ -201,7 +201,7 @@ module Mihari
201
201
  # @return [String]
202
202
  #
203
203
  def text
204
- tags = rule.tags
204
+ tags = rule.tags.map(&:name)
205
205
  tags = ["N/A"] if tags.empty?
206
206
  [
207
207
  "*#{rule.title}*",
@@ -214,15 +214,17 @@ module Mihari
214
214
  # @param [Array<Mihari::Models::Artifact>] artifacts
215
215
  #
216
216
  def call(artifacts)
217
- @artifacts = artifacts
218
-
219
217
  return if artifacts.empty?
220
218
 
219
+ @artifacts = artifacts
220
+
221
221
  notifier.post(text: text, attachments: attachments, mrkdwn: true)
222
222
  end
223
223
 
224
- def configuration_keys
225
- %w[slack_webhook_url slack_channel]
224
+ class << self
225
+ def configuration_keys
226
+ %w[slack_webhook_url slack_channel]
227
+ end
226
228
  end
227
229
  end
228
230
  end
@@ -9,9 +9,6 @@ module Mihari
9
9
  # @return [String, nil]
10
10
  attr_reader :api_key
11
11
 
12
- # @return [String, nil]
13
- attr_reader :api_version
14
-
15
12
  # @return [Array<Mihari::Models::Artifact>]
16
13
  attr_accessor :artifacts
17
14
 
@@ -25,7 +22,6 @@ module Mihari
25
22
 
26
23
  @url = params[:url] || Mihari.config.thehive_url
27
24
  @api_key = params[:api_key] || Mihari.config.thehive_api_key
28
- @api_version = params[:api_version] || Mihari.config.thehive_api_version
29
25
 
30
26
  @artifacts = []
31
27
  end
@@ -43,37 +39,23 @@ module Mihari
43
39
  # @param [Array<Mihari::Models::Artifact>] artifacts
44
40
  #
45
41
  def call(artifacts)
46
- @artifacts = artifacts
47
-
48
42
  return if artifacts.empty?
49
43
 
50
- client.alert payload
51
- end
44
+ @artifacts = artifacts
52
45
 
53
- #
54
- # Normalize API version for API client
55
- #
56
- # @param [String] version
57
- #
58
- # @return [String, nil]
59
- #
60
- def normalized_api_version
61
- @normalized_api_version ||= [].tap do |out|
62
- # v4 does not have version prefix in path (/api/)
63
- # v5 has version prefix in path (/api/v1/)
64
- table = { "" => nil, "v4" => nil, "v5" => "v1" }
65
- out << table[api_version.to_s.downcase]
66
- end.first
46
+ client.alert payload
67
47
  end
68
48
 
69
- def configuration_keys
70
- %w[thehive_url thehive_api_key]
49
+ class << self
50
+ def configuration_keys
51
+ %w[thehive_url thehive_api_key]
52
+ end
71
53
  end
72
54
 
73
55
  private
74
56
 
75
57
  def client
76
- @client ||= Clients::TheHive.new(url, api_key: api_key, api_version: normalized_api_version, timeout: timeout)
58
+ Clients::TheHive.new(url, api_key: api_key, api_version: "v1", timeout: timeout)
77
59
  end
78
60
 
79
61
  #
@@ -85,44 +67,12 @@ module Mihari
85
67
  !url.nil?
86
68
  end
87
69
 
88
- #
89
- # Check whether an API key is set or not
90
- #
91
- # @return [Boolean]
92
- #
93
- def api_key?
94
- !api_key.nil?
95
- end
96
-
97
70
  #
98
71
  # Build payload for alert
99
72
  #
100
73
  # @return [Hash]
101
74
  #
102
75
  def payload
103
- return v4_payload if normalized_api_version.nil?
104
-
105
- v5_payload
106
- end
107
-
108
- def v4_payload
109
- {
110
- title: rule.title,
111
- description: rule.description,
112
- artifacts: artifacts.map do |artifact|
113
- {
114
- data: artifact.data,
115
- data_type: artifact.data_type,
116
- message: rule.description
117
- }
118
- end,
119
- tags: rule.tags,
120
- type: "external",
121
- source: "mihari"
122
- }
123
- end
124
-
125
- def v5_payload
126
76
  {
127
77
  title: rule.title,
128
78
  description: rule.description,
@@ -136,7 +86,7 @@ module Mihari
136
86
  tags: rule.tags,
137
87
  type: "external",
138
88
  source: "mihari",
139
- source_ref: "1"
89
+ source_ref: SecureRandom.uuid
140
90
  }
141
91
  end
142
92
  end
@@ -4,7 +4,7 @@ require "erb"
4
4
 
5
5
  module Mihari
6
6
  module Emitters
7
- class PayloadTemplate < ERB
7
+ class ERBTemplate < ERB
8
8
  class << self
9
9
  def template
10
10
  %{
@@ -22,7 +22,7 @@ module Mihari
22
22
  ],
23
23
  "tags": [
24
24
  <% @rule.tags.each_with_index do |tag, idx| %>
25
- "<%= tag %>"
25
+ "<%= tag.name %>"
26
26
  <%= ',' if idx < (@rule.tags.length - 1) %>
27
27
  <% end %>
28
28
  ]
@@ -113,23 +113,23 @@ module Mihari
113
113
  #
114
114
  # @return [String]
115
115
  #
116
- def rendered_template
116
+ def render
117
117
  options = {}
118
118
  options[:template] = File.read(template) unless template.nil?
119
119
 
120
- payload_template = PayloadTemplate.new(
120
+ erb_template = ERBTemplate.new(
121
121
  artifacts: artifacts,
122
122
  rule: rule,
123
123
  options: options
124
124
  )
125
- payload_template.result
125
+ erb_template.result
126
126
  end
127
127
 
128
128
  #
129
129
  # @return [Hash]
130
130
  #
131
131
  def json
132
- JSON.parse rendered_template
132
+ JSON.parse render
133
133
  end
134
134
  end
135
135
  end
@@ -29,7 +29,7 @@ module Mihari
29
29
  private
30
30
 
31
31
  def client
32
- Clients::GooglePublicDNS.new
32
+ Clients::GooglePublicDNS.new(timeout: timeout)
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Enrichers
5
+ #
6
+ # MMDB enricher
7
+ #
8
+ class MMDB < Base
9
+ #
10
+ # Query MMDB
11
+ #
12
+ # @param [String] ip
13
+ #
14
+ # @return [Mihari::Structs::MMDB::Response]
15
+ #
16
+ def call(ip)
17
+ client.query ip
18
+ end
19
+ memo_wise :call
20
+
21
+ private
22
+
23
+ def client
24
+ @client ||= Clients::MMDB.new(timeout: timeout)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -14,16 +14,14 @@ module Mihari
14
14
  # @return [Mihari::Structs::Shodan::InternetDBResponse, nil]
15
15
  #
16
16
  def call(ip)
17
- url = "https://internetdb.shodan.io/#{ip}"
18
- res = http.get(url)
19
- Structs::Shodan::InternetDBResponse.from_dynamic! JSON.parse(res.body.to_s)
17
+ client.query ip
20
18
  end
21
19
  memo_wise :call
22
20
 
23
21
  private
24
22
 
25
- def http
26
- HTTP::Factory.build timeout: timeout
23
+ def client
24
+ @client ||= Clients::ShodanInternetDB.new(timeout: timeout)
27
25
  end
28
26
  end
29
27
  end
@@ -23,7 +23,7 @@ module Mihari
23
23
  # @return [Mihari::Models::WhoisRecord, nil]
24
24
  #
25
25
  def call(domain)
26
- _call PublicSuffix.domain(domain)
26
+ memoized_call PublicSuffix.domain(domain)
27
27
  end
28
28
 
29
29
  private
@@ -33,7 +33,7 @@ module Mihari
33
33
  #
34
34
  # @return [Mihari::Models::WhoisRecord, nil]
35
35
  #
36
- def _call(domain)
36
+ def memoized_call(domain)
37
37
  record = whois.lookup(domain)
38
38
  parser = record.parser
39
39
  return nil if parser.available?
@@ -47,7 +47,7 @@ module Mihari
47
47
  contacts: get_contacts(parser)
48
48
  )
49
49
  end
50
- memo_wise :_call
50
+ memo_wise :memoized_call
51
51
 
52
52
  #
53
53
  # @return [::Whois::Client]
@@ -3,11 +3,7 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class Alert < Grape::Entity
6
- expose :id,
7
- documentation: { type: String, required: true,
8
- desc: "String representation of the ID" } do |alert, _options|
9
- alert.id.to_s
10
- end
6
+ expose :id, documentation: { type: Integer, required: true }
11
7
  expose :rule_id, documentation: { type: String, required: true }, as: :ruleId
12
8
  expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
13
9
 
@@ -15,11 +11,8 @@ module Mihari
15
11
  expose :tags, using: Entities::Tag, documentation: { type: Entities::Tag, is_array: true, required: true }
16
12
  end
17
13
 
18
- class AlertsWithPagination < Grape::Entity
19
- expose :alerts, using: Entities::Alert, documentation: { type: Entities::Alert, is_array: true, required: true }
20
- expose :total, documentation: { type: Integer, required: true }
21
- expose :current_page, documentation: { type: Integer, required: true }, as: :currentPage
22
- expose :page_size, documentation: { type: Integer, required: true }, as: :pageSize
14
+ class AlertsWithPagination < Pagination
15
+ expose :results, using: Entities::Alert, documentation: { type: Entities::Alert, is_array: true, required: true }
23
16
  end
24
17
  end
25
18
  end
@@ -3,22 +3,17 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class BaseArtifact < Grape::Entity
6
- expose :id,
7
- documentation: { type: String, required: true,
8
- desc: "String representation of the ID" } do |artifact, _options|
9
- artifact.id.to_s
10
- end
6
+ expose :id, documentation: { type: Integer, required: true }
11
7
  expose :data, documentation: { type: String, required: true }
12
8
  expose :data_type, documentation: { type: String, required: true }, as: :dataType
13
9
  expose :source, documentation: { type: String, required: true }
14
10
  expose :query, documentation: { type: String, required: false }
15
- expose :tags, documentation: { type: String, is_array: true }
11
+ expose :metadata, documentation: { type: Hash }
12
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
16
13
  end
17
14
 
18
15
  class Artifact < BaseArtifact
19
- # NOTE: do not define metadata in BaseArtifact since metadata can be relatively big
20
- expose :metadata, documentation: { type: Hash }
21
-
16
+ expose :tags, using: Entities::Tag, documentation: { type: Entities::Tag, is_array: true, required: true }
22
17
  expose :autonomous_system, using: Entities::AutonomousSystem,
23
18
  documentation: { type: Entities::AutonomousSystem, required: false }, as: :autonomousSystem
24
19
  expose :geolocation, using: Entities::Geolocation, documentation: { type: Entities::Geolocation, required: false }
@@ -43,12 +38,9 @@ module Mihari
43
38
  end
44
39
  end
45
40
 
46
- class ArtifactsWithPagination < Grape::Entity
47
- expose :artifacts, using: Entities::Artifact,
41
+ class ArtifactsWithPagination < Pagination
42
+ expose :results, using: Entities::BaseArtifact,
48
43
  documentation: { type: Entities::Artifact, is_array: true, required: true }
49
- expose :total, documentation: { type: Integer, required: true }
50
- expose :current_page, documentation: { type: Integer, required: true }, as: :currentPage
51
- expose :page_size, documentation: { type: Integer, required: true }, as: :pageSize
52
44
  end
53
45
  end
54
46
  end
@@ -5,8 +5,8 @@ module Mihari
5
5
  class Config < Grape::Entity
6
6
  expose :name, documentation: { type: String, required: true }
7
7
  expose :type, documentation: { type: String, required: true }
8
- expose :values, documentation: { type: String, is_array: true, required: true }
9
- expose :is_configured, documentation: { type: Grape::API::Boolean, required: true }, as: :isConfigured
8
+ expose :items, documentation: { type: Hash, is_array: true, required: true }
9
+ expose :configured, documentation: { type: Grape::API::Boolean, required: true }
10
10
  end
11
11
  end
12
12
  end
@@ -4,6 +4,7 @@ module Mihari
4
4
  module Entities
5
5
  class CPE < Grape::Entity
6
6
  expose :cpe, documentation: { type: String, required: true }
7
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
7
8
  end
8
9
  end
9
10
  end
@@ -5,6 +5,7 @@ module Mihari
5
5
  class DnsRecord < Grape::Entity
6
6
  expose :resource, documentation: { type: String, required: true }
7
7
  expose :value, documentation: { type: String, required: true }
8
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
8
9
  end
9
10
  end
10
11
  end
@@ -5,6 +5,7 @@ module Mihari
5
5
  class Geolocation < Grape::Entity
6
6
  expose :country, documentation: { type: String, required: true }
7
7
  expose :country_code, documentation: { type: String, required: true }, as: :countryCode
8
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
8
9
  end
9
10
  end
10
11
  end
@@ -3,11 +3,9 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class IPAddress < Grape::Entity
6
- expose :ip, documentation: { type: String, required: true }
7
6
  expose :country_code, documentation: { type: String, required: true }, as: :countryCode
8
- expose :hostname, documentation: { type: String, required: false }
9
- expose :loc, documentation: { type: String, required: true }
10
7
  expose :asn, documentation: { type: Integer, required: false }
8
+ expose :loc, documentation: { type: String, required: false }
11
9
  end
12
10
  end
13
11
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Entities
5
+ class Message < Grape::Entity
6
+ expose :message, documentation: { type: String, required: true }
7
+ end
8
+
9
+ class ErrorMessage < Message
10
+ expose :detail, documentation: { type: Hash, required: false }
11
+ end
12
+
13
+ class QueueMessage < Message
14
+ expose :queued, documentation: { type: Grape::API::Boolean, required: true }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Entities
5
+ class Pagination < Grape::Entity
6
+ expose :total, documentation: { type: Integer, required: true }
7
+ expose :current_page, documentation: { type: Integer, required: true }, as: :currentPage
8
+ expose :page_size, documentation: { type: Integer, required: true }, as: :pageSize
9
+ end
10
+ end
11
+ end
@@ -4,6 +4,7 @@ module Mihari
4
4
  module Entities
5
5
  class Port < Grape::Entity
6
6
  expose :port, documentation: { type: Integer, required: true }
7
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
7
8
  end
8
9
  end
9
10
  end
@@ -4,6 +4,7 @@ module Mihari
4
4
  module Entities
5
5
  class ReverseDnsName < Grape::Entity
6
6
  expose :name, documentation: { type: String, required: true }
7
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
7
8
  end
8
9
  end
9
10
  end
@@ -2,16 +2,6 @@
2
2
 
3
3
  module Mihari
4
4
  module Entities
5
- class Query < Grape::Entity
6
- expose :analyzer, documentation: { type: String, required: true }
7
- expose :query, documentation: { type: String, required: true }
8
- expose :interval, documentation: { type: Integer, required: false }
9
- end
10
-
11
- class Emitter < Grape::Entity
12
- expose :emitter, documentation: { type: String, required: true }
13
- end
14
-
15
5
  class Rule < Grape::Entity
16
6
  expose :id, documentation: { type: String, required: true }
17
7
  expose :title, documentation: { type: String, required: true }
@@ -19,19 +9,11 @@ module Mihari
19
9
  expose :yaml, documentation: { type: String, required: true }
20
10
  expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
21
11
  expose :updated_at, documentation: { type: DateTime, required: true }, as: :updatedAt
22
-
23
12
  expose :tags, using: Entities::Tag, documentation: { type: Entities::Tag, is_array: true, required: true }
24
13
  end
25
14
 
26
- class RulesWithPagination < Grape::Entity
27
- expose :rules, using: Entities::Rule, documentation: { type: Entities::Rule, is_array: true, required: true }
28
- expose :total, documentation: { type: Integer, required: true }
29
- expose :current_page, documentation: { type: Integer, required: true }, as: :currentPage
30
- expose :page_size, documentation: { type: Integer, required: true }, as: :pageSize
31
- end
32
-
33
- class RuleIDs < Grape::Entity
34
- expose :rule_ids, documentation: { type: [String], required: true }, as: :ruleIds
15
+ class RulesWithPagination < Pagination
16
+ expose :results, using: Entities::Rule, documentation: { type: Entities::Rule, is_array: true, required: true }
35
17
  end
36
18
  end
37
19
  end
@@ -7,8 +7,8 @@ module Mihari
7
7
  expose :name, documentation: { type: String, required: true }
8
8
  end
9
9
 
10
- class Tags < Grape::Entity
11
- expose :tags, documentation: { type: [String], required: true }
10
+ class TagsWithPagination < Pagination
11
+ expose :results, using: Entities::Tag, documentation: { type: Entities::Tag, is_array: true, required: true }
12
12
  end
13
13
  end
14
14
  end
@@ -11,6 +11,7 @@ module Mihari
11
11
  expose :contacts, documentation: { type: Hash, is_array: true, required: true } do |whois_record, _options|
12
12
  whois_record.contacts.map(&:to_camelback_keys)
13
13
  end
14
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
14
15
  end
15
16
  end
16
17
  end
data/lib/mihari/errors.rb CHANGED
@@ -5,14 +5,12 @@ module Mihari
5
5
 
6
6
  class ValueError < Error; end
7
7
 
8
- class TypeError < Error; end
9
-
10
8
  class RetryableError < Error; end
11
9
 
12
- class FileNotFoundError < Error; end
13
-
14
10
  class ConfigurationError < Error; end
15
11
 
12
+ class IntegrityError < Error; end
13
+
16
14
  # errors for HTTP interactions
17
15
  class HTTPError < Error; end
18
16
 
data/lib/mihari/http.rb CHANGED
@@ -31,6 +31,7 @@ module Mihari
31
31
  #
32
32
  class Factory
33
33
  class << self
34
+ USER_AGENT = "mihari/#{Mihari::VERSION}".freeze
34
35
  #
35
36
  # @param [Integer, nil] timeout
36
37
  # @param [Hash] headers
@@ -41,6 +42,9 @@ module Mihari
41
42
  # @param [Object] raise_exception
42
43
  def build(headers: {}, timeout: nil, raise_exception: true)
43
44
  client = raise_exception ? ::HTTP.use(:better_error) : ::HTTP
45
+
46
+ headers["User-Agent"] ||= USER_AGENT
47
+
44
48
  client = client.headers(headers)
45
49
  client = client.timeout(timeout) unless timeout.nil?
46
50
  client
@@ -6,63 +6,31 @@ module Mihari
6
6
  # Alert model
7
7
  #
8
8
  class Alert < ActiveRecord::Base
9
- has_many :taggings, dependent: :destroy
10
- has_many :artifacts, dependent: :destroy
11
- has_many :tags, through: :taggings
12
-
13
9
  belongs_to :rule
14
10
 
15
- class << self
16
- #
17
- # Search alerts
18
- #
19
- # @param [Mihari::Structs::Filters::Alert::SearchFilterWithPagination] filter
20
- #
21
- # @return [Array<Alert>]
22
- #
23
- def search(filter)
24
- limit = filter.limit.to_i
25
- raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
26
-
27
- page = filter.page.to_i
28
- raise ArgumentError, "page should be bigger than zero" unless page.positive?
29
-
30
- offset = (page - 1) * limit
31
-
32
- relation = build_relation(filter.without_pagination)
33
- relation.limit(limit).offset(offset).order(id: :desc)
34
- end
35
-
36
- #
37
- # Count alerts
38
- #
39
- # @param [Mihari::Structs::Filters::Alert::SearchFilter] filter
40
- #
41
- # @return [Integer]
42
- #
43
- def count(filter)
44
- relation = build_relation(filter)
45
- relation.distinct("alerts.id").count
46
- end
47
-
48
- private
49
-
50
- #
51
- # @param [Mihari::Structs::Filters::Alert::SearchFilter] filter
52
- #
53
- # @return [Mihari::Models::Alert]
54
- #
55
- def build_relation(filter)
56
- relation = eager_load(:artifacts, :tags)
11
+ has_many :artifacts, dependent: :destroy
12
+ has_many :tags, through: :rule
13
+
14
+ include SearchCop
15
+ include Concerns::Searchable
16
+
17
+ search_scope :search do
18
+ attributes :id, :created_at, "rule.id", "rule.title", "rule.description"
19
+ attributes "artifact.data" => "artifacts.data"
20
+ attributes "artifact.data_type" => "artifacts.data_type"
21
+ attributes "artifact.source" => "artifacts.source"
22
+ attributes "artifact.query" => "artifacts.query"
23
+ attributes tag: "tags.name"
24
+ end
57
25
 
58
- relation = relation.where(artifacts: { data: filter.artifact }) if filter.artifact
59
- relation = relation.where(tags: { name: filter.tag }) if filter.tag
60
- relation = relation.where(rule_id: filter.rule_id) if filter.rule_id
61
- relation = relation.where("alerts.created_at >= ?", filter.from_at) if filter.from_at
62
- relation = relation.where("alerts.created_at <= ?", filter.to_at) if filter.to_at
26
+ class << self
27
+ # @!method search_by_filter(filter)
28
+ # @param [Mihari::Structs::Filters::Search] filter
29
+ # @return [Array<Mihari::Models::Alert>]
63
30
 
64
- relation
65
- end
31
+ # @!method count_by_filter(filter)
32
+ # @param [Mihari::Structs::Filters::Search] filter
33
+ # @return [Integer]
66
34
  end
67
35
  end
68
36
  end