mihari 4.1.2 → 4.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/README.md +1 -1
- data/lib/mihari/analyzers/base.rb +18 -10
- data/lib/mihari/analyzers/rule.rb +1 -1
- data/lib/mihari/cli/base.rb +0 -4
- data/lib/mihari/commands/init.rb +1 -1
- data/lib/mihari/commands/search.rb +11 -58
- data/lib/mihari/commands/validator.rb +1 -2
- data/lib/mihari/emitters/base.rb +5 -2
- data/lib/mihari/emitters/slack.rb +40 -4
- data/lib/mihari/enrichers/base.rb +5 -2
- data/lib/mihari/enrichers/ipinfo.rb +4 -3
- data/lib/mihari/{web/entities → entities}/alert.rb +0 -0
- data/lib/mihari/{web/entities → entities}/artifact.rb +0 -0
- data/lib/mihari/{web/entities → entities}/autonomous_system.rb +0 -0
- data/lib/mihari/{web/entities → entities}/command.rb +0 -0
- data/lib/mihari/{web/entities → entities}/config.rb +0 -0
- data/lib/mihari/{web/entities → entities}/dns.rb +0 -0
- data/lib/mihari/{web/entities → entities}/geolocation.rb +0 -0
- data/lib/mihari/{web/entities → entities}/ip_address.rb +0 -0
- data/lib/mihari/{web/entities → entities}/message.rb +0 -0
- data/lib/mihari/{web/entities → entities}/reverse_dns.rb +0 -0
- data/lib/mihari/{web/entities → entities}/rule.rb +0 -0
- data/lib/mihari/{web/entities → entities}/source.rb +0 -0
- data/lib/mihari/{web/entities → entities}/tag.rb +0 -0
- data/lib/mihari/{web/entities → entities}/whois.rb +0 -0
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/feed/reader.rb +11 -55
- data/lib/mihari/http.rb +94 -0
- data/lib/mihari/mixins/error_notification.rb +20 -0
- data/lib/mihari/mixins/retriable.rb +12 -2
- data/lib/mihari/mixins/rule.rb +1 -2
- data/lib/mihari/structs/ipinfo.rb +2 -3
- data/lib/mihari/structs/rule.rb +30 -0
- data/lib/mihari/structs/shodan.rb +9 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/api.rb +0 -20
- data/lib/mihari/web/app.rb +2 -2
- data/lib/mihari/web/endpoints/rules.rb +3 -1
- data/lib/mihari/web/middleware/error_notification_adapter.rb +19 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +1881 -165
- data/lib/mihari/web/public/static/css/app.43138058.css +1 -0
- data/lib/mihari/web/public/static/css/chunk-vendors.3ed9b08e.css +7 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.1fd0b4d7.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.5d5236fb.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.64b3730e.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.95a8a8af.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.6115ad71.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.f0203cfc.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-v4compatibility.e1023515.ttf +0 -0
- data/lib/mihari/web/public/static/js/app-legacy.46b666f0.js +2 -0
- data/lib/mihari/web/public/static/js/app-legacy.46b666f0.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.4818aedd.js +2 -0
- data/lib/mihari/web/public/static/js/app.4818aedd.js.map +1 -0
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.c99e452e.js +17 -0
- data/lib/mihari/web/public/static/js/chunk-vendors-legacy.c99e452e.js.map +1 -0
- data/lib/mihari/web/public/static/js/chunk-vendors.15e84e22.js +23 -0
- data/lib/mihari/web/public/static/js/chunk-vendors.15e84e22.js.map +1 -0
- data/lib/mihari.rb +63 -15
- data/mihari.gemspec +3 -3
- data/sig/lib/mihari/emitters/slack.rbs +29 -1
- data/sig/lib/mihari/feed/reader.rbs +2 -2
- data/sig/lib/mihari/http.rbs +65 -0
- data/sig/lib/mihari/mixins/error_notification.rbs +12 -0
- data/sig/lib/mihari/structs/rule.rbs +6 -0
- data/sig/lib/mihari.rbs +4 -8
- metadata +68 -55
- data/lib/mihari/cli/mixins/utils.rb +0 -72
- data/lib/mihari/emitters/stdout.rb +0 -22
- data/lib/mihari/notifiers/base.rb +0 -24
- data/lib/mihari/notifiers/exception_notifier.rb +0 -126
- data/lib/mihari/notifiers/slack.rb +0 -63
- data/sig/lib/mihari/cli/mixins/utils.rbs +0 -50
- data/sig/lib/mihari/notifiers/base.rbs +0 -18
- data/sig/lib/mihari/notifiers/exception_notifier.rbs +0 -75
- data/sig/lib/mihari/notifiers/slack.rbs +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 853724da55a4225403595284e6437da9816d016d99e380d239507e1eecdf35cd
|
|
4
|
+
data.tar.gz: 35e5ef84d38c67eaa1670a5e6261aed5c629966921ca3b14fb2509c9e3993c45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50855245bf70579da57c26859d2ab491e5238f7200c1de50cc7f46cafcd94c57e411e0fdbffdd60ddb00df2c8783ed051c87be24963fe10c5459784f337bd8d3
|
|
7
|
+
data.tar.gz: 436e6c0f3ceacb5b72f4212e83832759320cce109310736d87f6d1bfb49ac61f32a25a4d44cf133d2d5794466dea72bd69244997ccbbc6525418bc9aef403d0f
|
data/.github/workflows/test.yml
CHANGED
data/README.md
CHANGED
|
@@ -47,15 +47,23 @@ module Mihari
|
|
|
47
47
|
#
|
|
48
48
|
# Set artifacts & run emitters in parallel
|
|
49
49
|
#
|
|
50
|
-
# @return [nil]
|
|
50
|
+
# @return [Mihari::Alert, nil]
|
|
51
51
|
#
|
|
52
52
|
def run
|
|
53
|
+
unless configured?
|
|
54
|
+
class_name = self.class.to_s.split("::").last
|
|
55
|
+
raise ConfigurationError, "#{class_name} is not configured correctly"
|
|
56
|
+
end
|
|
57
|
+
|
|
53
58
|
with_db_connection do
|
|
54
59
|
set_enriched_artifacts
|
|
55
60
|
|
|
56
|
-
Parallel.
|
|
61
|
+
responses = Parallel.map(valid_emitters) do |emitter|
|
|
57
62
|
run_emitter emitter
|
|
58
63
|
end
|
|
64
|
+
|
|
65
|
+
# returns Mihari::Alert created by the database emitter
|
|
66
|
+
responses.find { |res| res.is_a?(Mihari::Alert) }
|
|
59
67
|
end
|
|
60
68
|
end
|
|
61
69
|
|
|
@@ -69,23 +77,26 @@ module Mihari
|
|
|
69
77
|
def run_emitter(emitter)
|
|
70
78
|
emitter.run(title: title, description: description, artifacts: enriched_artifacts, source: source, tags: tags)
|
|
71
79
|
rescue StandardError => e
|
|
72
|
-
|
|
80
|
+
Mihari.logger.info "Emission by #{emitter.class} is failed: #{e}"
|
|
73
81
|
end
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
class << self
|
|
84
|
+
def inherited(child)
|
|
85
|
+
super
|
|
86
|
+
Mihari.analyzers << child
|
|
87
|
+
end
|
|
77
88
|
end
|
|
78
89
|
|
|
79
90
|
#
|
|
80
91
|
# Normalize artifacts
|
|
81
|
-
# - Uniquefy artifacts by native #uniq
|
|
82
92
|
# - Convert data (string) into an artifact
|
|
83
93
|
# - Reject an invalid artifact
|
|
94
|
+
# - Uniquefy artifacts by data
|
|
84
95
|
#
|
|
85
96
|
# @return [Array<Mihari::Artifact>]
|
|
86
97
|
#
|
|
87
98
|
def normalized_artifacts
|
|
88
|
-
@normalized_artifacts ||= artifacts.compact.
|
|
99
|
+
@normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
|
|
89
100
|
# No need to set data_type manually
|
|
90
101
|
# It is set automatically in #initialize
|
|
91
102
|
artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
|
|
@@ -124,9 +135,6 @@ module Mihari
|
|
|
124
135
|
#
|
|
125
136
|
def set_enriched_artifacts
|
|
126
137
|
retry_on_error { enriched_artifacts }
|
|
127
|
-
rescue ArgumentError => e
|
|
128
|
-
klass = self.class.to_s.split("::").last.to_s
|
|
129
|
-
raise Error, "Please configure #{klass} settings properly. (#{e})"
|
|
130
138
|
end
|
|
131
139
|
|
|
132
140
|
#
|
|
@@ -145,7 +145,7 @@ module Mihari
|
|
|
145
145
|
instance = klass.new("dummy")
|
|
146
146
|
unless instance.configured?
|
|
147
147
|
klass_name = klass.to_s.split("::").last
|
|
148
|
-
raise
|
|
148
|
+
raise ConfigurationError, "#{klass_name} is not configured correctly"
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
151
|
end
|
data/lib/mihari/cli/base.rb
CHANGED
data/lib/mihari/commands/init.rb
CHANGED
|
@@ -5,12 +5,14 @@ module Mihari
|
|
|
5
5
|
module Search
|
|
6
6
|
include Mixins::Database
|
|
7
7
|
include Mixins::Rule
|
|
8
|
+
include Mixins::ErrorNotification
|
|
8
9
|
|
|
9
10
|
def self.included(thor)
|
|
10
11
|
thor.class_eval do
|
|
11
12
|
desc "search [RULE]", "Search by a rule"
|
|
12
13
|
def search_by_rule(path_or_id)
|
|
13
14
|
rule = load_rule(path_or_id)
|
|
15
|
+
|
|
14
16
|
# validate
|
|
15
17
|
begin
|
|
16
18
|
validate_rule! rule
|
|
@@ -18,22 +20,17 @@ module Mihari
|
|
|
18
20
|
raise e
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
analyzer = build_rule_analyzer(
|
|
23
|
-
title: rule[:title],
|
|
24
|
-
description: rule[:description],
|
|
25
|
-
queries: rule[:queries],
|
|
26
|
-
tags: rule[:tags],
|
|
27
|
-
allowed_data_types: rule[:allowed_data_types],
|
|
28
|
-
disallowed_data_values: rule[:disallowed_data_values],
|
|
29
|
-
id: rule.id
|
|
30
|
-
)
|
|
23
|
+
analyzer = rule.to_analyzer
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
with_error_notification do
|
|
26
|
+
alert = analyzer.run
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
if alert
|
|
29
|
+
data = Mihari::Entities::Alert.represent(alert)
|
|
30
|
+
puts JSON.pretty_generate(data.as_json)
|
|
31
|
+
else
|
|
32
|
+
Mihari.logger.info "There is no new artifact"
|
|
33
|
+
end
|
|
37
34
|
|
|
38
35
|
# record a rule
|
|
39
36
|
with_db_connection do
|
|
@@ -46,50 +43,6 @@ module Mihari
|
|
|
46
43
|
end
|
|
47
44
|
end
|
|
48
45
|
end
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
# Build a rule analyzer
|
|
54
|
-
#
|
|
55
|
-
# @param [String] title
|
|
56
|
-
# @param [String] description
|
|
57
|
-
# @param [Array<Hash>] queries
|
|
58
|
-
# @param [Array<String>, nil] tags
|
|
59
|
-
# @param [Array<String>, nil] allowed_data_types
|
|
60
|
-
# @param [Array<String>, nil] disallowed_data_values
|
|
61
|
-
#
|
|
62
|
-
# @return [Mihari::Analyzers::Rule]
|
|
63
|
-
#
|
|
64
|
-
def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, id: nil)
|
|
65
|
-
tags = [] if tags.nil?
|
|
66
|
-
allowed_data_types = ALLOWED_DATA_TYPES if allowed_data_types.nil?
|
|
67
|
-
disallowed_data_values = [] if disallowed_data_values.nil?
|
|
68
|
-
|
|
69
|
-
Analyzers::Rule.new(
|
|
70
|
-
title: title,
|
|
71
|
-
description: description,
|
|
72
|
-
tags: tags,
|
|
73
|
-
queries: queries,
|
|
74
|
-
allowed_data_types: allowed_data_types,
|
|
75
|
-
disallowed_data_values: disallowed_data_values,
|
|
76
|
-
id: id
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
#
|
|
81
|
-
# Run rule analyzer
|
|
82
|
-
#
|
|
83
|
-
# @param [Mihari::Analyzer::Rule] analyzer
|
|
84
|
-
#
|
|
85
|
-
# @return [nil]
|
|
86
|
-
#
|
|
87
|
-
def run_rule_analyzer(analyzer, ignore_old_artifacts: false, ignore_threshold: 0)
|
|
88
|
-
analyzer.ignore_old_artifacts = ignore_old_artifacts
|
|
89
|
-
analyzer.ignore_threshold = ignore_threshold
|
|
90
|
-
|
|
91
|
-
analyzer.run
|
|
92
|
-
end
|
|
93
46
|
end
|
|
94
47
|
end
|
|
95
48
|
end
|
|
@@ -13,8 +13,7 @@ module Mihari
|
|
|
13
13
|
|
|
14
14
|
begin
|
|
15
15
|
validate_rule! rule
|
|
16
|
-
|
|
17
|
-
puts rule.data.to_yaml
|
|
16
|
+
Mihari.logger.info "Valid format. The input is parsed as the following:\n#{rule.data.to_yaml}"
|
|
18
17
|
rescue RuleValidationError
|
|
19
18
|
nil
|
|
20
19
|
end
|
data/lib/mihari/emitters/base.rb
CHANGED
|
@@ -109,12 +109,48 @@ module Mihari
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
class Slack < Base
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
SLACK_WEBHOOK_URL_KEY = "SLACK_WEBHOOK_URL"
|
|
113
|
+
SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
|
|
114
|
+
DEFAULT_USERNAME = "mihari"
|
|
115
|
+
|
|
116
|
+
#
|
|
117
|
+
# Slack channel to post
|
|
118
|
+
#
|
|
119
|
+
# @return [String]
|
|
120
|
+
#
|
|
121
|
+
def slack_channel
|
|
122
|
+
Mihari.config.slack_channel || "#general"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
#
|
|
126
|
+
# Slack webhook URL
|
|
127
|
+
#
|
|
128
|
+
# @return [String]
|
|
129
|
+
#
|
|
130
|
+
def slack_webhook_url
|
|
131
|
+
Mihari.config.slack_webhook_url
|
|
114
132
|
end
|
|
115
133
|
|
|
134
|
+
#
|
|
135
|
+
# Check Slack webhook URL is set
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean]
|
|
138
|
+
#
|
|
139
|
+
def slack_webhook_url?
|
|
140
|
+
!Mihari.config.slack_webhook_url.nil?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# Check Slack webhook URL is set. Alias of #slack_webhook_url?.
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean]
|
|
147
|
+
#
|
|
116
148
|
def valid?
|
|
117
|
-
|
|
149
|
+
slack_webhook_url?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def notifier
|
|
153
|
+
@notifier ||= ::Slack::Notifier.new(slack_webhook_url, channel: slack_channel, username: DEFAULT_USERNAME)
|
|
118
154
|
end
|
|
119
155
|
|
|
120
156
|
#
|
|
@@ -155,7 +191,7 @@ module Mihari
|
|
|
155
191
|
attachments = to_attachments(artifacts)
|
|
156
192
|
text = to_text(title: title, description: description, tags: tags)
|
|
157
193
|
|
|
158
|
-
notifier.
|
|
194
|
+
notifier.post(text: text, attachments: attachments, mrkdwn: true)
|
|
159
195
|
end
|
|
160
196
|
|
|
161
197
|
private
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "net/https"
|
|
4
4
|
|
|
5
5
|
module Mihari
|
|
6
6
|
module Enrichers
|
|
@@ -34,11 +34,12 @@ module Mihari
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
begin
|
|
37
|
-
|
|
37
|
+
url = "https://ipinfo.io/#{ip}/json"
|
|
38
|
+
res = HTTP.get(url, headers: headers)
|
|
38
39
|
data = JSON.parse(res.body.to_s)
|
|
39
40
|
|
|
40
41
|
Structs::IPInfo::Response.from_dynamic! data
|
|
41
|
-
rescue
|
|
42
|
+
rescue HttpError
|
|
42
43
|
nil
|
|
43
44
|
end
|
|
44
45
|
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/lib/mihari/errors.rb
CHANGED
data/lib/mihari/feed/reader.rb
CHANGED
|
@@ -18,29 +18,21 @@ module Mihari
|
|
|
18
18
|
def read
|
|
19
19
|
return read_file(uri.path) if uri.scheme == "file"
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
res = nil
|
|
22
|
+
client = HTTP.new(uri, headers: http_request_headers, payload: http_request_payload, payload_type: http_request_payload_type)
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def get
|
|
27
|
-
uri.query = Addressable::URI.form_encode(http_request_payload)
|
|
28
|
-
get = Net::HTTP::Get.new(uri)
|
|
29
|
-
|
|
30
|
-
request(get)
|
|
31
|
-
end
|
|
24
|
+
res = client.get if http_request_method == "GET"
|
|
25
|
+
res = client.post if http_request_method == "POST"
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
post = Net::HTTP::Post.new(uri)
|
|
27
|
+
return [] if res.nil?
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
body = res.body
|
|
30
|
+
content_type = res["Content-Type"].to_s
|
|
31
|
+
if content_type.include?("application/json")
|
|
32
|
+
convert_as_json(body)
|
|
33
|
+
else
|
|
34
|
+
convert_as_csv(body)
|
|
41
35
|
end
|
|
42
|
-
|
|
43
|
-
request(post)
|
|
44
36
|
end
|
|
45
37
|
|
|
46
38
|
#
|
|
@@ -70,42 +62,6 @@ module Mihari
|
|
|
70
62
|
CSV.new(text_without_comments).to_a.reject(&:empty?)
|
|
71
63
|
end
|
|
72
64
|
|
|
73
|
-
def https_options
|
|
74
|
-
return { use_ssl: true } if uri.scheme == "https"
|
|
75
|
-
|
|
76
|
-
{}
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
# Make a HTTP request
|
|
81
|
-
#
|
|
82
|
-
# @param [Net::HTTPRequest] req
|
|
83
|
-
#
|
|
84
|
-
# @return [Array<Hash>]
|
|
85
|
-
#
|
|
86
|
-
def request(req)
|
|
87
|
-
Net::HTTP.start(uri.host, uri.port, https_options) do |http|
|
|
88
|
-
# set headers
|
|
89
|
-
http_request_headers.each do |k, v|
|
|
90
|
-
req[k] = v
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
response = http.request(req)
|
|
94
|
-
|
|
95
|
-
code = response.code.to_i
|
|
96
|
-
raise HttpError, "Unsupported response code returned: #{code}" if code != 200
|
|
97
|
-
|
|
98
|
-
body = response.body
|
|
99
|
-
|
|
100
|
-
content_type = response["Content-Type"].to_s
|
|
101
|
-
if content_type.include?("application/json")
|
|
102
|
-
convert_as_json(body)
|
|
103
|
-
else
|
|
104
|
-
convert_as_csv(body)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
65
|
#
|
|
110
66
|
# Read & convert a file
|
|
111
67
|
#
|
data/lib/mihari/http.rb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
class HTTP
|
|
5
|
+
attr_reader :uri, :headers, :payload_type, :payload
|
|
6
|
+
|
|
7
|
+
def initialize(uri, headers: {}, payload_type: nil, payload: {})
|
|
8
|
+
@uri = Addressable::URI.parse(uri)
|
|
9
|
+
@headers = headers
|
|
10
|
+
@payload_type = payload_type
|
|
11
|
+
@payload = payload
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Make a GET request
|
|
16
|
+
#
|
|
17
|
+
# @return [Net::HTTPResponse]
|
|
18
|
+
#
|
|
19
|
+
def get
|
|
20
|
+
uri.query = Addressable::URI.form_encode(payload)
|
|
21
|
+
get = Net::HTTP::Get.new(uri)
|
|
22
|
+
|
|
23
|
+
request(get)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Make a POST request
|
|
28
|
+
#
|
|
29
|
+
# @return [Net::HTTPResponse]
|
|
30
|
+
#
|
|
31
|
+
def post
|
|
32
|
+
post = Net::HTTP::Post.new(uri)
|
|
33
|
+
|
|
34
|
+
case payload_type
|
|
35
|
+
when "application/json"
|
|
36
|
+
headers["content-type"] = "application/json" unless headers.key?("content-type")
|
|
37
|
+
post.body = JSON.generate(payload)
|
|
38
|
+
when "application/x-www-form-urlencoded"
|
|
39
|
+
headers["content-type"] = "application/x-www-form-urlencoded" unless headers.key?("content-type")
|
|
40
|
+
post.set_form_data(payload)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
request(post)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
def get(uri, headers: {}, payload_type: nil, payload: {})
|
|
48
|
+
client = new(uri, headers: headers, payload_type: payload_type, payload: payload)
|
|
49
|
+
client.get
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def post(uri, headers: {}, payload_type: nil, payload: {})
|
|
53
|
+
client = new(uri, headers: headers, payload_type: payload_type, payload: payload)
|
|
54
|
+
client.post
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
#
|
|
61
|
+
# Get options for HTTP request
|
|
62
|
+
#
|
|
63
|
+
# @return [Hahs]
|
|
64
|
+
#
|
|
65
|
+
def https_options
|
|
66
|
+
return { use_ssl: true } if uri.scheme == "https"
|
|
67
|
+
|
|
68
|
+
{}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# Make a HTTP request
|
|
73
|
+
#
|
|
74
|
+
# @param [Net::HTTPRequest] req
|
|
75
|
+
#
|
|
76
|
+
# @return [Net::HTTPResponse]
|
|
77
|
+
#
|
|
78
|
+
def request(req)
|
|
79
|
+
Net::HTTP.start(uri.host, uri.port, https_options) do |http|
|
|
80
|
+
# set headers
|
|
81
|
+
headers.each do |k, v|
|
|
82
|
+
req[k] = v
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
res = http.request(req)
|
|
86
|
+
|
|
87
|
+
code = res.code.to_i
|
|
88
|
+
raise HttpError, "Unsupported response code returned: #{code}" if code != 200
|
|
89
|
+
|
|
90
|
+
res
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Mixins
|
|
5
|
+
module ErrorNotification
|
|
6
|
+
#
|
|
7
|
+
# Send an exception notification if there is any error in a block
|
|
8
|
+
#
|
|
9
|
+
# @return [Nil]
|
|
10
|
+
#
|
|
11
|
+
def with_error_notification
|
|
12
|
+
yield
|
|
13
|
+
rescue StandardError => e
|
|
14
|
+
Mihari.logger.error e
|
|
15
|
+
|
|
16
|
+
Sentry.capture_exception(e) if Sentry.initialized?
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -3,20 +3,30 @@
|
|
|
3
3
|
module Mihari
|
|
4
4
|
module Mixins
|
|
5
5
|
module Retriable
|
|
6
|
+
DEFAULT_ON = [
|
|
7
|
+
Errno::ECONNRESET,
|
|
8
|
+
Errno::ECONNABORTED,
|
|
9
|
+
Errno::EPIPE,
|
|
10
|
+
OpenSSL::SSL::SSLError,
|
|
11
|
+
Timeout::Error,
|
|
12
|
+
RetryableError
|
|
13
|
+
]
|
|
14
|
+
|
|
6
15
|
#
|
|
7
16
|
# Retry on error
|
|
8
17
|
#
|
|
9
18
|
# @param [Integer] times
|
|
10
19
|
# @param [Integer] interval
|
|
20
|
+
# @param [Array<StandardError>] on
|
|
11
21
|
#
|
|
12
22
|
# @return [nil]
|
|
13
23
|
#
|
|
14
|
-
def retry_on_error(times: 3, interval:
|
|
24
|
+
def retry_on_error(times: 3, interval: 5, on: DEFAULT_ON)
|
|
15
25
|
try = 0
|
|
16
26
|
begin
|
|
17
27
|
try += 1
|
|
18
28
|
yield
|
|
19
|
-
rescue
|
|
29
|
+
rescue *on => e
|
|
20
30
|
sleep interval
|
|
21
31
|
retry if try < times
|
|
22
32
|
raise e
|
data/lib/mihari/mixins/rule.rb
CHANGED
|
@@ -48,8 +48,7 @@ module Mihari
|
|
|
48
48
|
def validate_rule!(rule)
|
|
49
49
|
rule.validate!
|
|
50
50
|
rescue RuleValidationError => e
|
|
51
|
-
|
|
52
|
-
puts error_message.colorize(:red)
|
|
51
|
+
Mihari.logger.error "Failed to parse the input as a rule"
|
|
53
52
|
raise e
|
|
54
53
|
end
|
|
55
54
|
|
|
@@ -14,13 +14,12 @@ module Mihari
|
|
|
14
14
|
include Mixins::AutonomousSystem
|
|
15
15
|
|
|
16
16
|
def from_dynamic!(d)
|
|
17
|
+
d = d.deep_stringify_keys
|
|
17
18
|
d = Types::Hash[d]
|
|
18
19
|
|
|
19
20
|
asn = nil
|
|
20
21
|
asn_ = d.dig("asn", "asn")
|
|
21
|
-
unless asn_.nil?
|
|
22
|
-
asn = normalize_asn(asn_)
|
|
23
|
-
end
|
|
22
|
+
asn = normalize_asn(asn_) unless asn_.nil?
|
|
24
23
|
|
|
25
24
|
new(
|
|
26
25
|
ip: d.fetch("ip"),
|