mihari 5.4.9 → 5.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/analyzers/binaryedge.md +2 -2
- data/docs/analyzers/censys.md +3 -3
- data/docs/analyzers/circl.md +3 -3
- data/docs/analyzers/crtsh.md +2 -2
- data/docs/analyzers/dnstwister.md +1 -1
- data/docs/analyzers/feed.md +7 -7
- data/docs/analyzers/greynoise.md +2 -2
- data/docs/analyzers/hunterhow.md +4 -4
- data/docs/analyzers/index.md +13 -8
- data/docs/analyzers/onyphe.md +2 -2
- data/docs/analyzers/otx.md +2 -2
- data/docs/analyzers/passivetotal.md +7 -3
- data/docs/analyzers/pulsedive.md +2 -2
- data/docs/analyzers/securitytrails.md +6 -2
- data/docs/analyzers/shodan.md +2 -2
- data/docs/analyzers/urlscan.md +2 -2
- data/docs/analyzers/virustotal.md +6 -2
- data/docs/analyzers/virustotal_intelligence.md +6 -2
- data/docs/analyzers/zoomeye.md +3 -3
- data/docs/emitters/hive.md +4 -4
- data/docs/emitters/index.md +29 -0
- data/docs/emitters/misp.md +2 -2
- data/docs/emitters/slack.md +2 -7
- data/docs/emitters/webhook.md +4 -4
- data/docs/enrichers/index.md +29 -0
- data/docs/enrichers/ipinfo.md +7 -0
- data/docs/index.md +0 -2
- data/docs/installation.md +1 -1
- data/docs/rule.md +12 -15
- data/docs/usage.md +5 -2
- data/frontend/package-lock.json +294 -2772
- data/frontend/package.json +10 -10
- data/frontend/src/components/ErrorMessage.vue +0 -1
- data/frontend/src/components/alert/Alerts.vue +0 -1
- data/frontend/src/components/alert/AlertsWithPagination.vue +0 -1
- data/frontend/src/components/alert/AlertsWrapper.vue +0 -6
- data/frontend/src/components/alert/Form.vue +1 -3
- data/frontend/src/components/artifact/Artifact.vue +0 -17
- data/frontend/src/components/artifact/ArtifactWrapper.vue +0 -2
- data/frontend/src/components/artifact/WhoisRecord.vue +0 -3
- data/frontend/src/components/config/ConfigsWrapper.vue +0 -2
- data/frontend/src/components/rule/EditRule.vue +0 -3
- data/frontend/src/components/rule/EditRuleWrapper.vue +0 -2
- data/frontend/src/components/rule/Form.vue +1 -3
- data/frontend/src/components/rule/NewRule.vue +0 -3
- data/frontend/src/components/rule/Rule.vue +1 -7
- data/frontend/src/components/rule/RuleWrapper.vue +0 -2
- data/frontend/src/components/rule/RulesWrapper.vue +0 -6
- data/frontend/src/swagger.yaml +254 -254
- data/lib/mihari/analyzers/base.rb +7 -37
- data/lib/mihari/analyzers/binaryedge.rb +5 -1
- data/lib/mihari/analyzers/censys.rb +6 -1
- data/lib/mihari/analyzers/greynoise.rb +5 -1
- data/lib/mihari/analyzers/hunterhow.rb +5 -1
- data/lib/mihari/analyzers/onyphe.rb +5 -1
- data/lib/mihari/analyzers/passivetotal.rb +9 -0
- data/lib/mihari/analyzers/pulsedive.rb +1 -1
- data/lib/mihari/analyzers/rule.rb +55 -54
- data/lib/mihari/analyzers/securitytrails.rb +9 -0
- data/lib/mihari/analyzers/shodan.rb +5 -1
- data/lib/mihari/analyzers/urlscan.rb +5 -1
- data/lib/mihari/analyzers/virustotal.rb +11 -2
- data/lib/mihari/analyzers/virustotal_intelligence.rb +21 -1
- data/lib/mihari/analyzers/zoomeye.rb +7 -3
- data/lib/mihari/base.rb +69 -0
- data/lib/mihari/cli/main.rb +36 -0
- data/lib/mihari/clients/base.rb +7 -7
- data/lib/mihari/clients/binaryedge.rb +10 -4
- data/lib/mihari/clients/censys.rb +11 -4
- data/lib/mihari/clients/greynoise.rb +10 -4
- data/lib/mihari/clients/hunterhow.rb +10 -4
- data/lib/mihari/clients/misp.rb +3 -2
- data/lib/mihari/clients/onyphe.rb +10 -4
- data/lib/mihari/clients/shodan.rb +10 -4
- data/lib/mihari/clients/the_hive.rb +3 -2
- data/lib/mihari/clients/urlscan.rb +9 -3
- data/lib/mihari/clients/virustotal.rb +10 -4
- data/lib/mihari/clients/zoomeye.rb +11 -5
- data/lib/mihari/commands/alert.rb +6 -33
- data/lib/mihari/commands/rule.rb +7 -12
- data/lib/mihari/commands/search.rb +10 -38
- data/lib/mihari/config.rb +8 -0
- data/lib/mihari/constants.rb +3 -3
- data/lib/mihari/emitters/base.rb +22 -15
- data/lib/mihari/emitters/database.rb +1 -1
- data/lib/mihari/emitters/misp.rb +7 -6
- data/lib/mihari/emitters/slack.rb +24 -6
- data/lib/mihari/emitters/the_hive.rb +8 -7
- data/lib/mihari/emitters/webhook.rb +31 -29
- data/lib/mihari/enrichers/base.rb +25 -19
- data/lib/mihari/enrichers/google_public_dns.rb +38 -38
- data/lib/mihari/enrichers/ipinfo.rb +32 -34
- data/lib/mihari/enrichers/shodan.rb +18 -26
- data/lib/mihari/enrichers/whois.rb +121 -111
- data/lib/mihari/mixins/retriable.rb +4 -2
- data/lib/mihari/models/artifact.rb +37 -23
- data/lib/mihari/models/autonomous_system.rb +3 -2
- data/lib/mihari/models/cpe.rb +3 -2
- data/lib/mihari/models/dns.rb +3 -2
- data/lib/mihari/models/geolocation.rb +3 -2
- data/lib/mihari/models/port.rb +3 -2
- data/lib/mihari/models/reverse_dns.rb +3 -2
- data/lib/mihari/models/whois.rb +4 -3
- data/lib/mihari/schemas/analyzer.rb +24 -23
- data/lib/mihari/schemas/emitter.rb +32 -25
- data/lib/mihari/schemas/enricher.rb +21 -2
- data/lib/mihari/schemas/options.rb +27 -0
- data/lib/mihari/schemas/rule.rb +8 -4
- data/lib/mihari/services/alert_runner.rb +1 -1
- data/lib/mihari/services/rule_runner.rb +1 -11
- data/lib/mihari/types.rb +1 -14
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
- data/lib/mihari/web/public/assets/{index-33165282.css → index-56fc2187.css} +1 -1
- data/lib/mihari/web/public/assets/index-9cc489e6.js +1749 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +400 -400
- data/lib/mihari.rb +67 -37
- data/mihari.gemspec +3 -2
- data/mkdocs.yml +8 -6
- data/requirements.txt +1 -1
- metadata +24 -8
- data/lib/mihari/web/public/assets/index-a92abd57.js +0 -1740
@@ -7,15 +7,21 @@ module Mihari
|
|
7
7
|
# @param [String] base_url
|
8
8
|
# @param [String, nil] api_key
|
9
9
|
# @param [Hash] headers
|
10
|
-
# @param [Integer
|
10
|
+
# @param [Integer] pagnation_interval
|
11
11
|
# @param [Integer, nil] timeout
|
12
12
|
#
|
13
|
-
def initialize(
|
13
|
+
def initialize(
|
14
|
+
base_url = "https://api.binaryedge.io/v2",
|
15
|
+
api_key:,
|
16
|
+
headers: {},
|
17
|
+
pagination_interval: 0,
|
18
|
+
timeout: nil
|
19
|
+
)
|
14
20
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
15
21
|
|
16
22
|
headers["x-key"] = api_key
|
17
23
|
|
18
|
-
super(base_url, headers: headers,
|
24
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
19
25
|
end
|
20
26
|
|
21
27
|
#
|
@@ -52,7 +58,7 @@ module Mihari
|
|
52
58
|
|
53
59
|
break if res.events.length < res.pagesize
|
54
60
|
|
55
|
-
|
61
|
+
sleep_pagination_interval
|
56
62
|
end
|
57
63
|
end
|
58
64
|
end
|
@@ -10,16 +10,23 @@ module Mihari
|
|
10
10
|
# @param [String, nil] id
|
11
11
|
# @param [String, nil] secret
|
12
12
|
# @param [Hash] headers
|
13
|
-
# @param [Integer
|
13
|
+
# @param [Integer] pagination_interval
|
14
14
|
# @param [Integer, nil] timeout
|
15
15
|
#
|
16
|
-
def initialize(
|
16
|
+
def initialize(
|
17
|
+
base_url = "https://search.censys.io",
|
18
|
+
id:,
|
19
|
+
secret:,
|
20
|
+
headers: {},
|
21
|
+
pagination_interval: 0,
|
22
|
+
timeout: nil
|
23
|
+
)
|
17
24
|
raise(ArgumentError, "'id' argument is required") if id.nil?
|
18
25
|
raise(ArgumentError, "'secret' argument is required") if secret.nil?
|
19
26
|
|
20
27
|
headers["authorization"] = "Basic #{Base64.strict_encode64("#{id}:#{secret}")}"
|
21
28
|
|
22
|
-
super(base_url, headers: headers,
|
29
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
23
30
|
end
|
24
31
|
|
25
32
|
#
|
@@ -64,7 +71,7 @@ module Mihari
|
|
64
71
|
# So it needs to check both cases
|
65
72
|
break if cursor.nil? || cursor.empty?
|
66
73
|
|
67
|
-
|
74
|
+
sleep_pagination_interval
|
68
75
|
end
|
69
76
|
end
|
70
77
|
end
|
@@ -9,14 +9,20 @@ module Mihari
|
|
9
9
|
# @param [String] base_url
|
10
10
|
# @param [String, nil] api_key
|
11
11
|
# @param [Hash] headers
|
12
|
-
# @param [Integer
|
12
|
+
# @param [Integer] pagination_interval
|
13
13
|
# @param [Integer, nil] timeout
|
14
14
|
#
|
15
|
-
def initialize(
|
15
|
+
def initialize(
|
16
|
+
base_url = "https://api.greynoise.io",
|
17
|
+
api_key:,
|
18
|
+
headers: {},
|
19
|
+
pagination_interval: 0,
|
20
|
+
timeout: nil
|
21
|
+
)
|
16
22
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
17
23
|
|
18
24
|
headers["key"] = api_key
|
19
|
-
super(base_url, headers: headers,
|
25
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
20
26
|
end
|
21
27
|
|
22
28
|
#
|
@@ -53,7 +59,7 @@ module Mihari
|
|
53
59
|
scroll = res.scroll
|
54
60
|
break if scroll.nil?
|
55
61
|
|
56
|
-
|
62
|
+
sleep_pagination_interval
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
@@ -14,13 +14,19 @@ module Mihari
|
|
14
14
|
# @param [String] base_url
|
15
15
|
# @param [String, nil] api_key
|
16
16
|
# @param [Hash] headers
|
17
|
-
# @param [Integer
|
17
|
+
# @param [Integer] pagination_interval
|
18
18
|
# @param [Integer, nil] timeout
|
19
19
|
#
|
20
|
-
def initialize(
|
20
|
+
def initialize(
|
21
|
+
base_url = "https://api.hunter.how/",
|
22
|
+
api_key:,
|
23
|
+
headers: {},
|
24
|
+
pagination_interval: 0,
|
25
|
+
timeout: nil
|
26
|
+
)
|
21
27
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
22
28
|
|
23
|
-
super(base_url, headers: headers,
|
29
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
24
30
|
|
25
31
|
@api_key = api_key
|
26
32
|
end
|
@@ -77,7 +83,7 @@ module Mihari
|
|
77
83
|
|
78
84
|
break if res.data.list.length < page_size
|
79
85
|
|
80
|
-
|
86
|
+
sleep_pagination_interval
|
81
87
|
end
|
82
88
|
end
|
83
89
|
end
|
data/lib/mihari/clients/misp.rb
CHANGED
@@ -7,14 +7,15 @@ module Mihari
|
|
7
7
|
# @param [String] base_url
|
8
8
|
# @param [String, nil] api_key
|
9
9
|
# @param [Hash] headers
|
10
|
+
# @param [Integer, nil] timeout
|
10
11
|
#
|
11
|
-
def initialize(base_url, api_key:, headers: {})
|
12
|
+
def initialize(base_url, api_key:, headers: {}, timeout: nil)
|
12
13
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
13
14
|
|
14
15
|
headers["authorization"] = api_key
|
15
16
|
headers["accept"] = "application/json"
|
16
17
|
|
17
|
-
super(base_url, headers: headers)
|
18
|
+
super(base_url, headers: headers, timeout: timeout)
|
18
19
|
end
|
19
20
|
|
20
21
|
#
|
@@ -12,13 +12,19 @@ module Mihari
|
|
12
12
|
# @param [String] base_url
|
13
13
|
# @param [String, nil] api_key
|
14
14
|
# @param [Hash] headers
|
15
|
-
# @param [Integer
|
15
|
+
# @param [Integer] pagination_interval
|
16
16
|
# @param [Integer, nil] timeout
|
17
17
|
#
|
18
|
-
def initialize(
|
18
|
+
def initialize(
|
19
|
+
base_url = "https://www.onyphe.io",
|
20
|
+
api_key:,
|
21
|
+
headers: {},
|
22
|
+
pagination_interval: 0,
|
23
|
+
timeout: nil
|
24
|
+
)
|
19
25
|
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
20
26
|
|
21
|
-
super(base_url, headers: headers,
|
27
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
22
28
|
|
23
29
|
@api_key = api_key
|
24
30
|
end
|
@@ -50,7 +56,7 @@ module Mihari
|
|
50
56
|
|
51
57
|
break if res.total <= page * PAGE_SIZE
|
52
58
|
|
53
|
-
|
59
|
+
sleep_pagination_interval
|
54
60
|
end
|
55
61
|
end
|
56
62
|
end
|
@@ -12,13 +12,19 @@ module Mihari
|
|
12
12
|
# @param [String] base_url
|
13
13
|
# @param [String, nil] api_key
|
14
14
|
# @param [Hash] headers
|
15
|
-
# @param [Integer
|
15
|
+
# @param [Integer] pagination_interval
|
16
16
|
# @param [Integer, nil] timeout
|
17
17
|
#
|
18
|
-
def initialize(
|
18
|
+
def initialize(
|
19
|
+
base_url = "https://api.shodan.io",
|
20
|
+
api_key:,
|
21
|
+
headers: {},
|
22
|
+
pagination_interval: 0,
|
23
|
+
timeout: nil
|
24
|
+
)
|
19
25
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
20
26
|
|
21
|
-
super(base_url, headers: headers,
|
27
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
22
28
|
|
23
29
|
@api_key = api_key
|
24
30
|
end
|
@@ -57,7 +63,7 @@ module Mihari
|
|
57
63
|
|
58
64
|
break if res.total <= page * PAGE_SIZE
|
59
65
|
|
60
|
-
|
66
|
+
sleep_pagination_interval
|
61
67
|
rescue JSON::ParserError
|
62
68
|
# ignore JSON::ParserError
|
63
69
|
# ref. https://github.com/ninoseki/mihari/issues/197
|
@@ -8,14 +8,15 @@ module Mihari
|
|
8
8
|
# @param [String, nil] api_key
|
9
9
|
# @param [String, nil] api_version
|
10
10
|
# @param [Hash] headers
|
11
|
+
# @param [Integer, nil] timeout
|
11
12
|
#
|
12
|
-
def initialize(base_url, api_key:, api_version:, headers: {})
|
13
|
+
def initialize(base_url, api_key:, api_version:, headers: {}, timeout: nil)
|
13
14
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
14
15
|
|
15
16
|
base_url += "/#{api_version}" unless api_version.nil?
|
16
17
|
headers["authorization"] = "Bearer #{api_key}"
|
17
18
|
|
18
|
-
super(base_url, headers: headers)
|
19
|
+
super(base_url, headers: headers, timeout: timeout)
|
19
20
|
end
|
20
21
|
|
21
22
|
#
|
@@ -10,12 +10,18 @@ module Mihari
|
|
10
10
|
# @param [Interval, nil] interval
|
11
11
|
# @param [Interval, nil] timeout
|
12
12
|
#
|
13
|
-
def initialize(
|
13
|
+
def initialize(
|
14
|
+
base_url = "https://urlscan.io",
|
15
|
+
api_key:,
|
16
|
+
headers: {},
|
17
|
+
pagination_interval: 0,
|
18
|
+
timeout: nil
|
19
|
+
)
|
14
20
|
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
15
21
|
|
16
22
|
headers["api-key"] = api_key
|
17
23
|
|
18
|
-
super(base_url, headers: headers,
|
24
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
19
25
|
end
|
20
26
|
|
21
27
|
#
|
@@ -51,7 +57,7 @@ module Mihari
|
|
51
57
|
|
52
58
|
search_after = res.results.last.sort.join(",")
|
53
59
|
|
54
|
-
|
60
|
+
sleep_pagination_interval
|
55
61
|
end
|
56
62
|
end
|
57
63
|
end
|
@@ -7,15 +7,21 @@ module Mihari
|
|
7
7
|
# @param [String] base_url
|
8
8
|
# @param [String, nil] api_key
|
9
9
|
# @param [Hash] headers
|
10
|
-
# @param [Integer
|
10
|
+
# @param [Integer] pagination_interval
|
11
11
|
# @param [Integer, nil] timeout
|
12
12
|
#
|
13
|
-
def initialize(
|
13
|
+
def initialize(
|
14
|
+
base_url = "https://www.virustotal.com",
|
15
|
+
api_key:,
|
16
|
+
headers: {},
|
17
|
+
pagination_interval: 0,
|
18
|
+
timeout: nil
|
19
|
+
)
|
14
20
|
raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
|
15
21
|
|
16
22
|
headers["x-apikey"] = api_key
|
17
23
|
|
18
|
-
super(base_url, headers: headers,
|
24
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
19
25
|
end
|
20
26
|
|
21
27
|
#
|
@@ -66,7 +72,7 @@ module Mihari
|
|
66
72
|
cursor = res.meta.cursor
|
67
73
|
break if cursor.nil?
|
68
74
|
|
69
|
-
|
75
|
+
sleep_pagination_interval
|
70
76
|
end
|
71
77
|
end
|
72
78
|
end
|
@@ -11,14 +11,20 @@ module Mihari
|
|
11
11
|
# @param [String] base_url
|
12
12
|
# @param [String, nil] api_key
|
13
13
|
# @param [Hash] headers
|
14
|
-
# @param [Integer
|
14
|
+
# @param [Integer] pagination_interval
|
15
15
|
# @param [Integer, nil] timeout
|
16
16
|
#
|
17
|
-
def initialize(
|
17
|
+
def initialize(
|
18
|
+
base_url = "https://api.zoomeye.org",
|
19
|
+
api_key:,
|
20
|
+
headers: {},
|
21
|
+
pagination_interval: 0,
|
22
|
+
timeout: nil
|
23
|
+
)
|
18
24
|
raise(ArgumentError, "'api_key' argument is required") unless api_key
|
19
25
|
|
20
26
|
headers["api-key"] = api_key
|
21
|
-
super(base_url, headers: headers,
|
27
|
+
super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
|
22
28
|
end
|
23
29
|
|
24
30
|
#
|
@@ -59,7 +65,7 @@ module Mihari
|
|
59
65
|
total = res["total"].to_i
|
60
66
|
break if total <= page * PAGE_SIZE
|
61
67
|
|
62
|
-
|
68
|
+
sleep_pagination_interval
|
63
69
|
end
|
64
70
|
end
|
65
71
|
end
|
@@ -102,7 +108,7 @@ module Mihari
|
|
102
108
|
total = res["total"].to_i
|
103
109
|
break if total <= page * PAGE_SIZE
|
104
110
|
|
105
|
-
|
111
|
+
sleep_pagination_interval
|
106
112
|
end
|
107
113
|
end
|
108
114
|
end
|
@@ -14,30 +14,14 @@ module Mihari
|
|
14
14
|
#
|
15
15
|
def add(path)
|
16
16
|
Mihari::Database.with_db_connection do
|
17
|
-
builder =
|
17
|
+
builder = Services::AlertBuilder.new(path)
|
18
18
|
|
19
19
|
run_proxy_l = ->(proxy) { run_proxy proxy }
|
20
|
-
|
20
|
+
result = builder.result.bind(run_proxy_l)
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
alert = result.value!
|
26
|
-
data = Mihari::Entities::Alert.represent(alert)
|
27
|
-
puts JSON.pretty_generate(data.as_json)
|
28
|
-
return
|
29
|
-
end
|
30
|
-
|
31
|
-
failure = result.failure
|
32
|
-
case failure
|
33
|
-
when ValidationError
|
34
|
-
Mihari.logger.error "Failed to parse the input as an alert:"
|
35
|
-
Mihari.logger.error JSON.pretty_generate(failure.errors.to_h)
|
36
|
-
when StandardError
|
37
|
-
raise failure
|
38
|
-
else
|
39
|
-
Mihari.logger.info failure
|
40
|
-
end
|
22
|
+
alert = result.value!
|
23
|
+
data = Entities::Alert.represent(alert)
|
24
|
+
puts JSON.pretty_generate(data.as_json)
|
41
25
|
end
|
42
26
|
end
|
43
27
|
|
@@ -47,21 +31,10 @@ module Mihari
|
|
47
31
|
#
|
48
32
|
def run_proxy(proxy)
|
49
33
|
Dry::Monads::Try[StandardError] do
|
50
|
-
runner =
|
34
|
+
runner = Services::AlertRunner.new(proxy)
|
51
35
|
runner.run
|
52
36
|
end.to_result
|
53
37
|
end
|
54
|
-
|
55
|
-
#
|
56
|
-
# @param [Mihari::Alert, nil] alert_or_nil
|
57
|
-
#
|
58
|
-
def check_nil(alert_or_nil)
|
59
|
-
if alert_or_nil.nil?
|
60
|
-
Failure "There is no new artifact found"
|
61
|
-
else
|
62
|
-
Success alert_or_nil
|
63
|
-
end
|
64
|
-
end
|
65
38
|
end
|
66
39
|
end
|
67
40
|
end
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -8,7 +8,7 @@ module Mihari
|
|
8
8
|
class << self
|
9
9
|
def included(thor)
|
10
10
|
thor.class_eval do
|
11
|
-
include Dry::Monads[:
|
11
|
+
include Dry::Monads[:try, :result]
|
12
12
|
|
13
13
|
desc "validate [PATH]", "Validate a rule file"
|
14
14
|
#
|
@@ -18,16 +18,11 @@ module Mihari
|
|
18
18
|
#
|
19
19
|
def validate(path)
|
20
20
|
res = Dry::Monads::Try[ValidationError] do
|
21
|
-
Services::RuleProxy.from_yaml
|
22
|
-
end.fmap do |rule|
|
23
|
-
Mihari.logger.info "Valid format. The input is parsed as the following:"
|
24
|
-
Mihari.logger.info rule.data.to_yaml
|
21
|
+
Services::RuleProxy.from_yaml File.read(path)
|
25
22
|
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
Mihari.logger.error "Failed to parse the input as a rule:"
|
30
|
-
Mihari.logger.error JSON.pretty_generate(res.exception.errors.to_h)
|
24
|
+
rule = res.value!
|
25
|
+
puts rule.data.to_yaml
|
31
26
|
end
|
32
27
|
|
33
28
|
desc "init [PATH]", "Initialize a new rule file"
|
@@ -43,14 +38,14 @@ module Mihari
|
|
43
38
|
|
44
39
|
initialize_rule path
|
45
40
|
|
46
|
-
|
41
|
+
puts "A new rule file has been initialized: #{path}."
|
47
42
|
end
|
48
43
|
|
49
44
|
no_commands do
|
50
45
|
#
|
51
46
|
# @return [Mihari::Services::Rule]
|
52
47
|
#
|
53
|
-
def
|
48
|
+
def rule
|
54
49
|
Services::RuleProxy.from_yaml File.read(File.expand_path("../templates/rule.yml.erb", __dir__))
|
55
50
|
end
|
56
51
|
|
@@ -63,7 +58,7 @@ module Mihari
|
|
63
58
|
# @return [nil]
|
64
59
|
#
|
65
60
|
def initialize_rule(path, files = Dry::Files.new)
|
66
|
-
files.write(path,
|
61
|
+
files.write(path, rule.yaml)
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
@@ -6,10 +6,10 @@ module Mihari
|
|
6
6
|
class << self
|
7
7
|
def included(thor)
|
8
8
|
thor.class_eval do
|
9
|
-
include Dry::Monads[:
|
9
|
+
include Dry::Monads[:try, :result]
|
10
10
|
|
11
|
-
desc "search [PATH_OR_ID]", "Search by a rule"
|
12
|
-
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force
|
11
|
+
desc "search [PATH_OR_ID]", "Search by a rule (Outputs null if there is no new finding)"
|
12
|
+
method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force overwriting a rule"
|
13
13
|
#
|
14
14
|
# Search by a rule
|
15
15
|
#
|
@@ -21,27 +21,12 @@ module Mihari
|
|
21
21
|
|
22
22
|
check_diff_l = ->(rule) { check_diff rule }
|
23
23
|
update_and_run_l = ->(runner) { update_and_run runner }
|
24
|
-
check_nil_l = ->(alert_or_nil) { check_nil alert_or_nil }
|
25
24
|
|
26
|
-
result = builder.result.bind(check_diff_l).bind(update_and_run_l)
|
25
|
+
result = builder.result.bind(check_diff_l).bind(update_and_run_l)
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
puts JSON.pretty_generate(data.as_json)
|
32
|
-
return
|
33
|
-
end
|
34
|
-
|
35
|
-
failure = result.failure
|
36
|
-
case failure
|
37
|
-
when ValidationError
|
38
|
-
Mihari.logger.error "Failed to parse the input as a rule:"
|
39
|
-
Mihari.logger.error JSON.pretty_generate(failure.errors.to_h)
|
40
|
-
when StandardError
|
41
|
-
raise failure
|
42
|
-
else
|
43
|
-
Mihari.logger.info failure
|
44
|
-
end
|
27
|
+
alert = result.value!
|
28
|
+
data = Entities::Alert.represent(alert)
|
29
|
+
puts JSON.pretty_generate(data.as_json)
|
45
30
|
end
|
46
31
|
end
|
47
32
|
|
@@ -51,27 +36,14 @@ module Mihari
|
|
51
36
|
#
|
52
37
|
def check_diff(rule)
|
53
38
|
force_overwrite = options["force_overwrite"] || false
|
54
|
-
|
55
|
-
|
39
|
+
message = "There is a diff in the rule. Are you sure you want to overwrite the rule? (y/n)"
|
40
|
+
runner = Services::RuleRunner.new(rule)
|
56
41
|
|
57
|
-
if runner.diff? && !force_overwrite && !yes?(message)
|
58
|
-
return Failure("Stop overwriting the rule (#{rule.id})")
|
59
|
-
end
|
42
|
+
exit 0 if runner.diff? && !force_overwrite && !yes?(message)
|
60
43
|
|
61
44
|
Success runner
|
62
45
|
end
|
63
46
|
|
64
|
-
#
|
65
|
-
# @param [Mihari::Alert, nil] alert_or_nil
|
66
|
-
#
|
67
|
-
def check_nil(alert_or_nil)
|
68
|
-
if alert_or_nil.nil?
|
69
|
-
Failure "There is no new artifact found"
|
70
|
-
else
|
71
|
-
Success alert_or_nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
47
|
#
|
76
48
|
# @param [Mihari::Services::RuleRunner] runner
|
77
49
|
#
|
data/lib/mihari/config.rb
CHANGED
@@ -90,6 +90,12 @@ module Mihari
|
|
90
90
|
# @return [Integer]
|
91
91
|
attr_reader :retry_times
|
92
92
|
|
93
|
+
# @return [Boolean]
|
94
|
+
attr_reader :retry_exponential_backoff
|
95
|
+
|
96
|
+
# @return [Integer]
|
97
|
+
attr_reader :pagination_interval
|
98
|
+
|
93
99
|
# @return [Integer]
|
94
100
|
attr_reader :pagination_limit
|
95
101
|
|
@@ -152,7 +158,9 @@ module Mihari
|
|
152
158
|
|
153
159
|
@retry_times = ENV.fetch("RETRY_TIMES", 3).to_i
|
154
160
|
@retry_interval = ENV.fetch("RETRY_INTERVAL", 5).to_i
|
161
|
+
@retry_exponential_backoff = ENV.fetch("RETRY_EXPONENTIAL_BACKOFF", true).to_s.downcase == "true"
|
155
162
|
|
163
|
+
@pagination_interval = ENV.fetch("PAGINATION_INTERVAL", 0).to_i
|
156
164
|
@pagination_limit = ENV.fetch("PAGINATION_LIMIT", 100).to_i
|
157
165
|
|
158
166
|
@ignore_error = ENV.fetch("IGNORE_ERROR", false)
|
data/lib/mihari/constants.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
# @return [Array<String>]
|
5
|
-
DEFAULT_DATA_TYPES =
|
5
|
+
DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
|
6
6
|
|
7
7
|
# @return [Array<Hash>]
|
8
|
-
DEFAULT_EMITTERS = %w[database
|
8
|
+
DEFAULT_EMITTERS = %w[database].map { |name| { emitter: name } }.freeze
|
9
9
|
|
10
10
|
# @return [Array<Hash>]
|
11
|
-
DEFAULT_ENRICHERS =
|
11
|
+
DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
|
12
12
|
end
|
data/lib/mihari/emitters/base.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
module Emitters
|
5
|
-
class Base
|
5
|
+
class Base < Mihari::Base
|
6
6
|
include Dry::Monads[:result, :try]
|
7
7
|
|
8
8
|
include Mixins::Configurable
|
@@ -17,36 +17,43 @@ module Mihari
|
|
17
17
|
#
|
18
18
|
# @param [Array<Mihari::Artifact>] artifacts
|
19
19
|
# @param [Mihari::Services::RuleProxy] rule
|
20
|
-
# @param [Hash]
|
20
|
+
# @param [Hash, nil] options
|
21
|
+
# @param [Hash] **_params
|
21
22
|
#
|
22
|
-
def initialize(artifacts:, rule:, **
|
23
|
+
def initialize(artifacts:, rule:, options: nil, **_params)
|
24
|
+
super(options: options)
|
25
|
+
|
23
26
|
@artifacts = artifacts
|
24
27
|
@rule = rule
|
25
28
|
end
|
26
29
|
|
27
|
-
class << self
|
28
|
-
def inherited(child)
|
29
|
-
super
|
30
|
-
Mihari.emitters << child
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
30
|
# @return [Boolean]
|
35
31
|
def valid?
|
36
32
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
37
33
|
end
|
38
34
|
|
39
|
-
def run
|
40
|
-
retry_on_error { emit }
|
41
|
-
end
|
42
|
-
|
43
35
|
def result
|
44
|
-
Try[StandardError]
|
36
|
+
Try[StandardError] do
|
37
|
+
retry_on_error(
|
38
|
+
times: retry_times,
|
39
|
+
interval: retry_interval,
|
40
|
+
exponential_backoff: retry_exponential_backoff
|
41
|
+
) do
|
42
|
+
emit
|
43
|
+
end
|
44
|
+
end.to_result
|
45
45
|
end
|
46
46
|
|
47
47
|
def emit
|
48
48
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
49
49
|
end
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def inherited(child)
|
53
|
+
super
|
54
|
+
Mihari.emitters << child
|
55
|
+
end
|
56
|
+
end
|
50
57
|
end
|
51
58
|
end
|
52
59
|
end
|
@@ -13,7 +13,7 @@ module Mihari
|
|
13
13
|
# @return [Mihari::Alert, nil]
|
14
14
|
#
|
15
15
|
def emit
|
16
|
-
return
|
16
|
+
return if artifacts.empty?
|
17
17
|
|
18
18
|
tags = rule.tags.filter_map { |name| Tag.find_or_create_by(name: name) }.uniq
|
19
19
|
taggings = tags.map { |tag| Tagging.new(tag_id: tag.id) }
|