mihari 5.4.3 → 5.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -25
- data/docs/alternatives.md +5 -0
- data/docs/analyzers/binaryedge.md +21 -0
- data/docs/analyzers/censys.md +23 -0
- data/docs/analyzers/circl.md +29 -0
- data/docs/analyzers/crtsh.md +25 -0
- data/docs/analyzers/dnstwister.md +23 -0
- data/docs/analyzers/feed.md +49 -0
- data/docs/analyzers/greynoise.md +21 -0
- data/docs/analyzers/hunterhow.md +25 -0
- data/docs/analyzers/index.md +79 -0
- data/docs/analyzers/onyphe.md +21 -0
- data/docs/analyzers/otx.md +23 -0
- data/docs/analyzers/passivetotal.md +36 -0
- data/docs/analyzers/pulsedive.md +23 -0
- data/docs/analyzers/securitytrails.md +32 -0
- data/docs/analyzers/shodan.md +21 -0
- data/docs/analyzers/urlscan.md +23 -0
- data/docs/analyzers/virustotal.md +34 -0
- data/docs/analyzers/virustotal_intelligence.md +22 -0
- data/docs/analyzers/zoomeye.md +25 -0
- data/docs/configuration.md +35 -0
- data/docs/emitters/database.md +22 -0
- data/docs/emitters/hive.md +18 -0
- data/docs/emitters/index.md +7 -0
- data/docs/emitters/misp.md +16 -0
- data/docs/emitters/slack.md +16 -0
- data/docs/emitters/webhook.md +63 -0
- data/docs/enrichers/google_public_dns.md +19 -0
- data/docs/enrichers/index.md +6 -0
- data/docs/enrichers/ipinfo.md +19 -0
- data/docs/enrichers/shodan.md +22 -0
- data/docs/enrichers/whois.md +17 -0
- data/docs/github_actions.md +43 -0
- data/docs/index.md +13 -0
- data/docs/installation.md +31 -0
- data/docs/requirements.md +20 -0
- data/docs/rule.md +165 -0
- data/docs/tags.md +3 -0
- data/docs/usage.md +100 -0
- data/frontend/package-lock.json +2414 -1516
- data/frontend/package.json +22 -22
- data/lib/mihari/analyzers/base.rb +25 -10
- data/lib/mihari/analyzers/binaryedge.rb +1 -7
- data/lib/mihari/analyzers/circl.rb +1 -1
- data/lib/mihari/analyzers/dnstwister.rb +1 -1
- data/lib/mihari/analyzers/otx.rb +1 -1
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +18 -13
- data/lib/mihari/analyzers/securitytrails.rb +1 -1
- data/lib/mihari/analyzers/urlscan.rb +1 -1
- data/lib/mihari/analyzers/virustotal.rb +1 -1
- data/lib/mihari/analyzers/zoomeye.rb +1 -1
- data/lib/mihari/clients/binaryedge.rb +4 -7
- data/lib/mihari/clients/crtsh.rb +1 -3
- data/lib/mihari/clients/publsedive.rb +1 -1
- data/lib/mihari/clients/shodan.rb +2 -2
- data/lib/mihari/commands/alert.rb +42 -13
- data/lib/mihari/commands/rule.rb +11 -7
- data/lib/mihari/commands/search.rb +54 -22
- data/lib/mihari/config.rb +5 -0
- data/lib/mihari/emitters/base.rb +9 -3
- data/lib/mihari/emitters/slack.rb +1 -1
- data/lib/mihari/enrichers/base.rb +13 -0
- data/lib/mihari/enrichers/google_public_dns.rb +16 -1
- data/lib/mihari/enrichers/ipinfo.rb +9 -13
- data/lib/mihari/enrichers/shodan.rb +1 -2
- data/lib/mihari/enrichers/whois.rb +2 -2
- data/lib/mihari/errors.rb +16 -10
- data/lib/mihari/feed/parser.rb +2 -2
- data/lib/mihari/models/artifact.rb +1 -1
- data/lib/mihari/models/autonomous_system.rb +11 -5
- data/lib/mihari/models/cpe.rb +10 -4
- data/lib/mihari/models/dns.rb +11 -16
- data/lib/mihari/models/geolocation.rb +11 -5
- data/lib/mihari/models/port.rb +10 -4
- data/lib/mihari/models/reverse_dns.rb +10 -4
- data/lib/mihari/models/whois.rb +4 -1
- data/lib/mihari/schemas/analyzer.rb +1 -0
- data/lib/mihari/services/alert_builder.rb +43 -0
- data/lib/mihari/services/alert_proxy.rb +7 -25
- data/lib/mihari/services/alert_runner.rb +9 -0
- data/lib/mihari/services/rule_builder.rb +47 -0
- data/lib/mihari/services/rule_proxy.rb +5 -61
- data/lib/mihari/services/rule_runner.rb +9 -4
- data/lib/mihari/structs/binaryedge.rb +89 -0
- data/lib/mihari/structs/shodan.rb +2 -1
- data/lib/mihari/structs/urlscan.rb +1 -3
- data/lib/mihari/structs/virustotal_intelligence.rb +1 -3
- data/lib/mihari/type_checker.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/alerts.rb +33 -15
- data/lib/mihari/web/endpoints/artifacts.rb +53 -25
- data/lib/mihari/web/endpoints/configs.rb +2 -2
- data/lib/mihari/web/endpoints/ip_addresses.rb +3 -5
- data/lib/mihari/web/endpoints/rules.rb +97 -71
- data/lib/mihari/web/endpoints/tags.rb +15 -5
- data/lib/mihari/web/public/assets/index-0a5a47bf.js +1740 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +419 -382
- data/lib/mihari.rb +4 -0
- data/mihari.gemspec +6 -5
- data/mkdocs.yml +35 -0
- data/requirements.txt +2 -0
- metadata +70 -12
- data/lib/mihari/web/public/assets/index-4d7eda9f.js +0 -1738
data/lib/mihari/emitters/base.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Emitters
|
5
5
|
class Base
|
6
|
+
include Dry::Monads[:result, :try]
|
7
|
+
|
6
8
|
include Mixins::Configurable
|
7
9
|
include Mixins::Retriable
|
8
10
|
|
@@ -34,11 +36,15 @@ module Mihari
|
|
34
36
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
35
37
|
end
|
36
38
|
|
37
|
-
def run
|
38
|
-
retry_on_error { emit
|
39
|
+
def run
|
40
|
+
retry_on_error { emit }
|
41
|
+
end
|
42
|
+
|
43
|
+
def result
|
44
|
+
Try[StandardError] { run }.to_result
|
39
45
|
end
|
40
46
|
|
41
|
-
def emit
|
47
|
+
def emit
|
42
48
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
43
49
|
end
|
44
50
|
end
|
@@ -6,10 +6,23 @@ module Mihari
|
|
6
6
|
include Mixins::Configurable
|
7
7
|
|
8
8
|
class << self
|
9
|
+
include Dry::Monads[:result, :try]
|
10
|
+
|
9
11
|
def inherited(child)
|
10
12
|
super
|
11
13
|
Mihari.enrichers << child
|
12
14
|
end
|
15
|
+
|
16
|
+
def query_result(value)
|
17
|
+
Try[StandardError] { query(value) }.to_result
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# @param [String] value
|
22
|
+
#
|
23
|
+
def query(value)
|
24
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
25
|
+
end
|
13
26
|
end
|
14
27
|
|
15
28
|
# @return [Boolean]
|
@@ -11,15 +11,30 @@ module Mihari
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class << self
|
14
|
+
include Dry::Monads[:result]
|
15
|
+
|
14
16
|
#
|
15
17
|
# Query Google Public DNS
|
16
18
|
#
|
17
19
|
# @param [String] name
|
20
|
+
#
|
21
|
+
# @return [Array<Mihari::Structs::Shodan::GooglePublicDNS::Response>]
|
22
|
+
#
|
23
|
+
def query(name)
|
24
|
+
%w[A AAAA CNAME TXT NS].filter_map do |resource_type|
|
25
|
+
query_by_type(name, resource_type)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Query Google Public DNS by resource type
|
31
|
+
#
|
32
|
+
# @param [String] name
|
18
33
|
# @param [String] resource_type
|
19
34
|
#
|
20
35
|
# @return [Mihari::Structs::Shodan::GooglePublicDNS::Response, nil]
|
21
36
|
#
|
22
|
-
def
|
37
|
+
def query_by_type(name, resource_type)
|
23
38
|
url = "https://dns.google/resolve"
|
24
39
|
params = { name: name, type: resource_type }
|
25
40
|
res = HTTP.get(url, params: params)
|
@@ -17,6 +17,7 @@ module Mihari
|
|
17
17
|
end
|
18
18
|
|
19
19
|
class << self
|
20
|
+
include Dry::Monads[:result]
|
20
21
|
include Memist::Memoizable
|
21
22
|
|
22
23
|
#
|
@@ -28,20 +29,15 @@ module Mihari
|
|
28
29
|
#
|
29
30
|
def query(ip)
|
30
31
|
headers = {}
|
32
|
+
|
31
33
|
token = Mihari.config.ipinfo_api_key
|
32
|
-
unless token.nil?
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
data = JSON.parse(res.body.to_s)
|
40
|
-
|
41
|
-
Structs::IPInfo::Response.from_dynamic! data
|
42
|
-
rescue HTTPError
|
43
|
-
nil
|
44
|
-
end
|
34
|
+
headers[:authorization] = "Bearer #{token}" unless token.nil?
|
35
|
+
|
36
|
+
url = "https://ipinfo.io/#{ip}/json"
|
37
|
+
res = HTTP.get(url, headers: headers)
|
38
|
+
data = JSON.parse(res.body.to_s)
|
39
|
+
|
40
|
+
Structs::IPInfo::Response.from_dynamic! data
|
45
41
|
end
|
46
42
|
memoize :query
|
47
43
|
end
|
@@ -11,6 +11,7 @@ module Mihari
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class << self
|
14
|
+
include Dry::Monads[:result]
|
14
15
|
include Memist::Memoizable
|
15
16
|
|
16
17
|
#
|
@@ -26,8 +27,6 @@ module Mihari
|
|
26
27
|
data = JSON.parse(res.body.to_s)
|
27
28
|
|
28
29
|
Structs::Shodan::InternetDBResponse.from_dynamic! data
|
29
|
-
rescue HTTPError
|
30
|
-
nil
|
31
30
|
end
|
32
31
|
memoize :query
|
33
32
|
end
|
@@ -14,6 +14,8 @@ module Mihari
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class << self
|
17
|
+
include Dry::Monads[:result]
|
18
|
+
|
17
19
|
#
|
18
20
|
# Query IAIA Whois API
|
19
21
|
#
|
@@ -47,8 +49,6 @@ module Mihari
|
|
47
49
|
# set memo
|
48
50
|
@memo[domain] = whois_record
|
49
51
|
whois_record
|
50
|
-
rescue ::Whois::Error, ::Whois::ParserError, Timeout::Error
|
51
|
-
nil
|
52
52
|
end
|
53
53
|
|
54
54
|
def reset_cache
|
data/lib/mihari/errors.rb
CHANGED
@@ -3,22 +3,14 @@
|
|
3
3
|
module Mihari
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
|
-
class
|
6
|
+
class ValueError < Error; end
|
7
7
|
|
8
|
-
class
|
8
|
+
class TypeError < Error; end
|
9
9
|
|
10
10
|
class RetryableError < Error; end
|
11
11
|
|
12
12
|
class FileNotFoundError < Error; end
|
13
13
|
|
14
|
-
class FeedParseError < Error; end
|
15
|
-
|
16
|
-
class RuleValidationError < Error; end
|
17
|
-
|
18
|
-
class AlertValidationError < Error; end
|
19
|
-
|
20
|
-
class YAMLSyntaxError < Error; end
|
21
|
-
|
22
14
|
class ConfigurationError < Error; end
|
23
15
|
|
24
16
|
# errors for HTTP interactions
|
@@ -49,4 +41,18 @@ module Mihari
|
|
49
41
|
@body = body
|
50
42
|
end
|
51
43
|
end
|
44
|
+
|
45
|
+
class ValidationError < Error
|
46
|
+
attr_reader :errors
|
47
|
+
|
48
|
+
#
|
49
|
+
# @param [String] msg
|
50
|
+
# @param [Dry::Schema::MessageSet] errors
|
51
|
+
#
|
52
|
+
def initialize(msg, errors)
|
53
|
+
super(msg)
|
54
|
+
|
55
|
+
@errors = errors
|
56
|
+
end
|
57
|
+
end
|
52
58
|
end
|
data/lib/mihari/feed/parser.rb
CHANGED
@@ -25,8 +25,8 @@ module Mihari
|
|
25
25
|
def parse(selector)
|
26
26
|
parsed = data.instance_eval(selector)
|
27
27
|
|
28
|
-
raise
|
29
|
-
raise
|
28
|
+
raise TypeError unless parsed.is_a?(Array) || parsed.is_a?(Enumerator)
|
29
|
+
raise TypeError unless parsed.all?(String)
|
30
30
|
|
31
31
|
parsed.to_a
|
32
32
|
end
|
@@ -5,6 +5,8 @@ module Mihari
|
|
5
5
|
belongs_to :artifact
|
6
6
|
|
7
7
|
class << self
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
8
10
|
#
|
9
11
|
# Build AS
|
10
12
|
#
|
@@ -13,11 +15,15 @@ module Mihari
|
|
13
15
|
# @return [Mihari::AutonomousSystem, nil]
|
14
16
|
#
|
15
17
|
def build_by_ip(ip)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
result = Enrichers::IPInfo.query_result(ip).bind do |res|
|
19
|
+
value = res&.asn
|
20
|
+
if value.nil?
|
21
|
+
Success nil
|
22
|
+
else
|
23
|
+
Success new(asn: value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
result.value_or nil
|
21
27
|
end
|
22
28
|
end
|
23
29
|
end
|
data/lib/mihari/models/cpe.rb
CHANGED
@@ -5,6 +5,8 @@ module Mihari
|
|
5
5
|
belongs_to :artifact
|
6
6
|
|
7
7
|
class << self
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
8
10
|
#
|
9
11
|
# Build CPEs
|
10
12
|
#
|
@@ -13,10 +15,14 @@ module Mihari
|
|
13
15
|
# @return [Array<Mihari::CPE>]
|
14
16
|
#
|
15
17
|
def build_by_ip(ip)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
result = Enrichers::Shodan.query_result(ip).bind do |res|
|
19
|
+
if res.nil?
|
20
|
+
Success []
|
21
|
+
else
|
22
|
+
Success(res.cpes.map { |cpe| new(cpe: cpe) })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
result.value_or []
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
data/lib/mihari/models/dns.rb
CHANGED
@@ -5,6 +5,8 @@ module Mihari
|
|
5
5
|
belongs_to :artifact
|
6
6
|
|
7
7
|
class << self
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
8
10
|
#
|
9
11
|
# Build DNS records
|
10
12
|
#
|
@@ -13,23 +15,16 @@ module Mihari
|
|
13
15
|
# @return [Array<Mihari::DnsRecord>]
|
14
16
|
#
|
15
17
|
def build_by_domain(domain)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def get_values(domain, resource_type)
|
27
|
-
response = Enrichers::GooglePublicDNS.query(domain, resource_type)
|
28
|
-
answers = response.answers || []
|
29
|
-
|
30
|
-
answers.filter_map do |answer|
|
31
|
-
new(resource: answer.resource_type, value: answer.data)
|
18
|
+
result = Enrichers::GooglePublicDNS.query_result(domain).bind do |responses|
|
19
|
+
Success(
|
20
|
+
responses.map do |res|
|
21
|
+
res.answers.map do |answer|
|
22
|
+
new(resource: answer.resource_type, value: answer.data)
|
23
|
+
end
|
24
|
+
end.flatten
|
25
|
+
)
|
32
26
|
end
|
27
|
+
result.value_or []
|
33
28
|
end
|
34
29
|
end
|
35
30
|
end
|
@@ -7,6 +7,8 @@ module Mihari
|
|
7
7
|
belongs_to :artifact
|
8
8
|
|
9
9
|
class << self
|
10
|
+
include Dry::Monads[:result]
|
11
|
+
|
10
12
|
#
|
11
13
|
# Build Geolocation
|
12
14
|
#
|
@@ -15,11 +17,15 @@ module Mihari
|
|
15
17
|
# @return [Mihari::Geolocation, nil]
|
16
18
|
#
|
17
19
|
def build_by_ip(ip)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
result = Enrichers::IPInfo.query_result(ip).bind do |res|
|
21
|
+
value = res&.country_code
|
22
|
+
if value.nil?
|
23
|
+
Success nil
|
24
|
+
else
|
25
|
+
Success new(country: NormalizeCountry(value, to: :short), country_code: value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
result.value_or nil
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
data/lib/mihari/models/port.rb
CHANGED
@@ -5,6 +5,8 @@ module Mihari
|
|
5
5
|
belongs_to :artifact
|
6
6
|
|
7
7
|
class << self
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
8
10
|
#
|
9
11
|
# Build ports
|
10
12
|
#
|
@@ -13,10 +15,14 @@ module Mihari
|
|
13
15
|
# @return [Array<Mihari::Port>]
|
14
16
|
#
|
15
17
|
def build_by_ip(ip)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
result = Enrichers::Shodan.query_result(ip).bind do |res|
|
19
|
+
if res.nil?
|
20
|
+
Success []
|
21
|
+
else
|
22
|
+
Success(res.ports.map { |port| new(port: port) })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
result.value_or []
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
@@ -5,6 +5,8 @@ module Mihari
|
|
5
5
|
belongs_to :artifact
|
6
6
|
|
7
7
|
class << self
|
8
|
+
include Dry::Monads[:result]
|
9
|
+
|
8
10
|
#
|
9
11
|
# Build reverse DNS names
|
10
12
|
#
|
@@ -13,10 +15,14 @@ module Mihari
|
|
13
15
|
# @return [Array<Mihari::ReverseDnsName>]
|
14
16
|
#
|
15
17
|
def build_by_ip(ip)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
result = Enrichers::Shodan.query_result(ip).bind do |res|
|
19
|
+
if res.nil?
|
20
|
+
Success []
|
21
|
+
else
|
22
|
+
Success(res.hostnames.map { |name| new(name: name) })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
result.value_or []
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
data/lib/mihari/models/whois.rb
CHANGED
@@ -7,6 +7,8 @@ module Mihari
|
|
7
7
|
@memo = {}
|
8
8
|
|
9
9
|
class << self
|
10
|
+
include Dry::Monads[:result]
|
11
|
+
|
10
12
|
#
|
11
13
|
# Build whois record
|
12
14
|
#
|
@@ -15,7 +17,8 @@ module Mihari
|
|
15
17
|
# @return [WhoisRecord, nil]
|
16
18
|
#
|
17
19
|
def build_by_domain(domain)
|
18
|
-
Enrichers::Whois.
|
20
|
+
result = Enrichers::Whois.query_result(domain)
|
21
|
+
result.value_or nil
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -7,6 +7,7 @@ module Mihari
|
|
7
7
|
optional(:pagination_limit).value(:integer).default(Mihari.config.pagination_limit)
|
8
8
|
optional(:retry_times).value(:integer).default(Mihari.config.retry_times)
|
9
9
|
optional(:retry_interval).value(:integer).default(Mihari.config.retry_interval)
|
10
|
+
optional(:ignore_error).value(:bool).default(Mihari.config.ignore_error)
|
10
11
|
end
|
11
12
|
|
12
13
|
AnalyzerWithoutAPIKey = Dry::Schema.Params do
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
require "erb"
|
6
|
+
require "pathname"
|
7
|
+
require "yaml"
|
8
|
+
|
9
|
+
module Mihari
|
10
|
+
module Services
|
11
|
+
class AlertBuilder
|
12
|
+
include Dry::Monads[:result, :try]
|
13
|
+
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :path
|
16
|
+
|
17
|
+
#
|
18
|
+
# Initialize
|
19
|
+
#
|
20
|
+
# @param [String] path
|
21
|
+
#
|
22
|
+
def initialize(path)
|
23
|
+
@path = path
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# @return [Hash]
|
28
|
+
#
|
29
|
+
def data
|
30
|
+
raise ArgumentError, "#{path} does not exist" unless Pathname(path).exist?
|
31
|
+
|
32
|
+
YAML.safe_load(
|
33
|
+
ERB.new(File.read(path)).result,
|
34
|
+
permitted_classes: [Date, Symbol]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def result
|
39
|
+
Try[StandardError] { AlertProxy.new(data) }.to_result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "json"
|
4
|
+
|
3
5
|
module Mihari
|
4
6
|
module Services
|
5
7
|
class AlertProxy
|
@@ -16,10 +18,9 @@ module Mihari
|
|
16
18
|
#
|
17
19
|
def initialize(data)
|
18
20
|
@data = data.deep_symbolize_keys
|
19
|
-
|
20
21
|
@errors = nil
|
21
22
|
|
22
|
-
validate
|
23
|
+
validate!
|
23
24
|
end
|
24
25
|
|
25
26
|
#
|
@@ -31,21 +32,14 @@ module Mihari
|
|
31
32
|
!@errors.empty?
|
32
33
|
end
|
33
34
|
|
34
|
-
def validate
|
35
|
+
def validate!
|
35
36
|
contract = Schemas::AlertContract.new
|
36
37
|
result = contract.call(data)
|
37
38
|
|
38
39
|
@data = result.to_h
|
39
40
|
@errors = result.errors
|
40
|
-
end
|
41
|
-
|
42
|
-
def validate!
|
43
|
-
return unless errors?
|
44
41
|
|
45
|
-
|
46
|
-
Mihari.logger.error JSON.pretty_generate(errors.to_h)
|
47
|
-
|
48
|
-
raise AlertValidationError, errors
|
42
|
+
raise ValidationError.new("Validation failed", errors) if errors?
|
49
43
|
end
|
50
44
|
|
51
45
|
def [](key)
|
@@ -74,7 +68,7 @@ module Mihari
|
|
74
68
|
# @return [Mihari::Services::RuleProxy]
|
75
69
|
#
|
76
70
|
def rule
|
77
|
-
@rule ||= Services::RuleProxy.
|
71
|
+
@rule ||= Services::RuleProxy.new(Mihari::Rule.find(rule_id).data)
|
78
72
|
end
|
79
73
|
|
80
74
|
class << self
|
@@ -86,19 +80,7 @@ module Mihari
|
|
86
80
|
# @return [Mihari::Services::Alert]
|
87
81
|
#
|
88
82
|
def from_yaml(yaml)
|
89
|
-
|
90
|
-
rescue Psych::SyntaxError => e
|
91
|
-
raise YAMLSyntaxError, e.message
|
92
|
-
end
|
93
|
-
|
94
|
-
# @param [String] path
|
95
|
-
#
|
96
|
-
# @return [Mihari::Services::Alert, nil]
|
97
|
-
#
|
98
|
-
def from_path(path)
|
99
|
-
return nil unless Pathname(path).exist?
|
100
|
-
|
101
|
-
from_yaml File.read(path)
|
83
|
+
new YAML.safe_load(yaml, permitted_classes: [Date, Symbol])
|
102
84
|
end
|
103
85
|
end
|
104
86
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module Mihari
|
4
4
|
module Services
|
5
5
|
class AlertRunner
|
6
|
+
include Dry::Monads[:result, :try]
|
7
|
+
|
6
8
|
# @return [Mihari::Services::AlertProxy]
|
7
9
|
attr_reader :alert
|
8
10
|
|
@@ -17,6 +19,13 @@ module Mihari
|
|
17
19
|
emitter = Mihari::Emitters::Database.new(artifacts: alert.artifacts, rule: alert.rule)
|
18
20
|
emitter.emit
|
19
21
|
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# @return [Dry::Monads::Result::Success<Mihari::Alert, nil>, Dry::Monads::Result::Failure]
|
25
|
+
#
|
26
|
+
def result
|
27
|
+
Try[StandardError] { run }.to_result
|
28
|
+
end
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
require "erb"
|
5
|
+
require "pathname"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
module Mihari
|
9
|
+
module Services
|
10
|
+
class RuleBuilder
|
11
|
+
include Dry::Monads[:result, :try]
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :path_or_id
|
15
|
+
|
16
|
+
#
|
17
|
+
# Initialize
|
18
|
+
#
|
19
|
+
# @param [String] path_or_id
|
20
|
+
#
|
21
|
+
def initialize(path_or_id)
|
22
|
+
@path_or_id = path_or_id
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
#
|
28
|
+
def data
|
29
|
+
if Mihari::Rule.exists?(path_or_id)
|
30
|
+
rule = Mihari::Rule.find(path_or_id)
|
31
|
+
return rule.data
|
32
|
+
end
|
33
|
+
|
34
|
+
raise ArgumentError, "#{path_or_id} does not exist" unless Pathname(path_or_id).exist?
|
35
|
+
|
36
|
+
YAML.safe_load(
|
37
|
+
ERB.new(File.read(path_or_id)).result,
|
38
|
+
permitted_classes: [Date, Symbol]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def result
|
43
|
+
Try[StandardError] { RuleProxy.new(data) }.to_result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|