mihari 5.4.9 → 5.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/docs/analyzers/binaryedge.md +2 -2
  3. data/docs/analyzers/censys.md +3 -3
  4. data/docs/analyzers/circl.md +3 -3
  5. data/docs/analyzers/crtsh.md +2 -2
  6. data/docs/analyzers/dnstwister.md +1 -1
  7. data/docs/analyzers/feed.md +7 -7
  8. data/docs/analyzers/greynoise.md +2 -2
  9. data/docs/analyzers/hunterhow.md +4 -4
  10. data/docs/analyzers/index.md +13 -8
  11. data/docs/analyzers/onyphe.md +2 -2
  12. data/docs/analyzers/otx.md +2 -2
  13. data/docs/analyzers/passivetotal.md +7 -3
  14. data/docs/analyzers/pulsedive.md +2 -2
  15. data/docs/analyzers/securitytrails.md +6 -2
  16. data/docs/analyzers/shodan.md +2 -2
  17. data/docs/analyzers/urlscan.md +2 -2
  18. data/docs/analyzers/virustotal.md +6 -2
  19. data/docs/analyzers/virustotal_intelligence.md +6 -2
  20. data/docs/analyzers/zoomeye.md +3 -3
  21. data/docs/emitters/hive.md +4 -4
  22. data/docs/emitters/index.md +29 -0
  23. data/docs/emitters/misp.md +2 -2
  24. data/docs/emitters/slack.md +2 -7
  25. data/docs/emitters/webhook.md +4 -4
  26. data/docs/enrichers/index.md +29 -0
  27. data/docs/enrichers/ipinfo.md +7 -0
  28. data/docs/index.md +0 -2
  29. data/docs/installation.md +1 -1
  30. data/docs/rule.md +12 -15
  31. data/docs/usage.md +5 -2
  32. data/frontend/package-lock.json +294 -2772
  33. data/frontend/package.json +10 -10
  34. data/frontend/src/components/ErrorMessage.vue +0 -1
  35. data/frontend/src/components/alert/Alerts.vue +0 -1
  36. data/frontend/src/components/alert/AlertsWithPagination.vue +0 -1
  37. data/frontend/src/components/alert/AlertsWrapper.vue +0 -6
  38. data/frontend/src/components/alert/Form.vue +1 -3
  39. data/frontend/src/components/artifact/Artifact.vue +0 -17
  40. data/frontend/src/components/artifact/ArtifactWrapper.vue +0 -2
  41. data/frontend/src/components/artifact/WhoisRecord.vue +0 -3
  42. data/frontend/src/components/config/ConfigsWrapper.vue +0 -2
  43. data/frontend/src/components/rule/EditRule.vue +0 -3
  44. data/frontend/src/components/rule/EditRuleWrapper.vue +0 -2
  45. data/frontend/src/components/rule/Form.vue +1 -3
  46. data/frontend/src/components/rule/NewRule.vue +0 -3
  47. data/frontend/src/components/rule/Rule.vue +1 -7
  48. data/frontend/src/components/rule/RuleWrapper.vue +0 -2
  49. data/frontend/src/components/rule/RulesWrapper.vue +0 -6
  50. data/frontend/src/swagger.yaml +254 -254
  51. data/lib/mihari/analyzers/base.rb +7 -37
  52. data/lib/mihari/analyzers/binaryedge.rb +5 -1
  53. data/lib/mihari/analyzers/censys.rb +6 -1
  54. data/lib/mihari/analyzers/greynoise.rb +5 -1
  55. data/lib/mihari/analyzers/hunterhow.rb +5 -1
  56. data/lib/mihari/analyzers/onyphe.rb +5 -1
  57. data/lib/mihari/analyzers/passivetotal.rb +9 -0
  58. data/lib/mihari/analyzers/pulsedive.rb +1 -1
  59. data/lib/mihari/analyzers/rule.rb +55 -54
  60. data/lib/mihari/analyzers/securitytrails.rb +9 -0
  61. data/lib/mihari/analyzers/shodan.rb +5 -1
  62. data/lib/mihari/analyzers/urlscan.rb +5 -1
  63. data/lib/mihari/analyzers/virustotal.rb +11 -2
  64. data/lib/mihari/analyzers/virustotal_intelligence.rb +21 -1
  65. data/lib/mihari/analyzers/zoomeye.rb +7 -3
  66. data/lib/mihari/base.rb +69 -0
  67. data/lib/mihari/cli/main.rb +36 -0
  68. data/lib/mihari/clients/base.rb +7 -7
  69. data/lib/mihari/clients/binaryedge.rb +10 -4
  70. data/lib/mihari/clients/censys.rb +11 -4
  71. data/lib/mihari/clients/greynoise.rb +10 -4
  72. data/lib/mihari/clients/hunterhow.rb +10 -4
  73. data/lib/mihari/clients/misp.rb +3 -2
  74. data/lib/mihari/clients/onyphe.rb +10 -4
  75. data/lib/mihari/clients/shodan.rb +10 -4
  76. data/lib/mihari/clients/the_hive.rb +3 -2
  77. data/lib/mihari/clients/urlscan.rb +9 -3
  78. data/lib/mihari/clients/virustotal.rb +10 -4
  79. data/lib/mihari/clients/zoomeye.rb +11 -5
  80. data/lib/mihari/commands/alert.rb +6 -33
  81. data/lib/mihari/commands/rule.rb +7 -12
  82. data/lib/mihari/commands/search.rb +10 -38
  83. data/lib/mihari/config.rb +8 -0
  84. data/lib/mihari/constants.rb +3 -3
  85. data/lib/mihari/emitters/base.rb +22 -15
  86. data/lib/mihari/emitters/database.rb +1 -1
  87. data/lib/mihari/emitters/misp.rb +7 -6
  88. data/lib/mihari/emitters/slack.rb +24 -6
  89. data/lib/mihari/emitters/the_hive.rb +8 -7
  90. data/lib/mihari/emitters/webhook.rb +31 -29
  91. data/lib/mihari/enrichers/base.rb +25 -19
  92. data/lib/mihari/enrichers/google_public_dns.rb +38 -38
  93. data/lib/mihari/enrichers/ipinfo.rb +32 -34
  94. data/lib/mihari/enrichers/shodan.rb +18 -26
  95. data/lib/mihari/enrichers/whois.rb +121 -111
  96. data/lib/mihari/mixins/retriable.rb +4 -2
  97. data/lib/mihari/models/artifact.rb +37 -23
  98. data/lib/mihari/models/autonomous_system.rb +3 -2
  99. data/lib/mihari/models/cpe.rb +3 -2
  100. data/lib/mihari/models/dns.rb +3 -2
  101. data/lib/mihari/models/geolocation.rb +3 -2
  102. data/lib/mihari/models/port.rb +3 -2
  103. data/lib/mihari/models/reverse_dns.rb +3 -2
  104. data/lib/mihari/models/whois.rb +4 -3
  105. data/lib/mihari/schemas/analyzer.rb +24 -23
  106. data/lib/mihari/schemas/emitter.rb +32 -25
  107. data/lib/mihari/schemas/enricher.rb +21 -2
  108. data/lib/mihari/schemas/options.rb +27 -0
  109. data/lib/mihari/schemas/rule.rb +8 -4
  110. data/lib/mihari/services/alert_runner.rb +1 -1
  111. data/lib/mihari/services/rule_runner.rb +1 -11
  112. data/lib/mihari/types.rb +1 -14
  113. data/lib/mihari/version.rb +1 -1
  114. data/lib/mihari/web/endpoints/ip_addresses.rb +1 -1
  115. data/lib/mihari/web/public/assets/{index-33165282.css → index-56fc2187.css} +1 -1
  116. data/lib/mihari/web/public/assets/index-9cc489e6.js +1749 -0
  117. data/lib/mihari/web/public/index.html +2 -2
  118. data/lib/mihari/web/public/redoc-static.html +400 -400
  119. data/lib/mihari.rb +67 -37
  120. data/mihari.gemspec +3 -2
  121. data/mkdocs.yml +8 -6
  122. data/requirements.txt +1 -1
  123. metadata +24 -8
  124. 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, nil] interval
10
+ # @param [Integer] pagnation_interval
11
11
  # @param [Integer, nil] timeout
12
12
  #
13
- def initialize(base_url = "https://api.binaryedge.io/v2", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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, nil] interval
13
+ # @param [Integer] pagination_interval
14
14
  # @param [Integer, nil] timeout
15
15
  #
16
- def initialize(base_url = "https://search.censys.io", id:, secret:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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, nil] interval
12
+ # @param [Integer] pagination_interval
13
13
  # @param [Integer, nil] timeout
14
14
  #
15
- def initialize(base_url = "https://api.greynoise.io", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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, nil] interval
17
+ # @param [Integer] pagination_interval
18
18
  # @param [Integer, nil] timeout
19
19
  #
20
- def initialize(base_url = "https://api.hunter.how/", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
86
+ sleep_pagination_interval
81
87
  end
82
88
  end
83
89
  end
@@ -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, nil] interval
15
+ # @param [Integer] pagination_interval
16
16
  # @param [Integer, nil] timeout
17
17
  #
18
- def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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, nil] interval
15
+ # @param [Integer] pagination_interval
16
16
  # @param [Integer, nil] timeout
17
17
  #
18
- def initialize(base_url = "https://api.shodan.io", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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(base_url = "https://urlscan.io", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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, nil] interval
10
+ # @param [Integer] pagination_interval
11
11
  # @param [Integer, nil] timeout
12
12
  #
13
- def initialize(base_url = "https://www.virustotal.com", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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, nil] interval
14
+ # @param [Integer] pagination_interval
15
15
  # @param [Integer, nil] timeout
16
16
  #
17
- def initialize(base_url = "https://api.zoomeye.org", api_key:, headers: {}, interval: nil, timeout: nil)
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, interval: interval, timeout: timeout)
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
- sleep_interval
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
- sleep_interval
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 = Mihari::Services::AlertBuilder.new(path)
17
+ builder = Services::AlertBuilder.new(path)
18
18
 
19
19
  run_proxy_l = ->(proxy) { run_proxy proxy }
20
- check_nil_l = ->(alert_or_nil) { check_nil alert_or_nil }
20
+ result = builder.result.bind(run_proxy_l)
21
21
 
22
- result = builder.result.bind(run_proxy_l).bind(check_nil_l)
23
-
24
- if result.success?
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 = Mihari::Services::AlertRunner.new(proxy)
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
@@ -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[:result, :try]
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(File.read(path))
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
- return unless res.error?
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
- Mihari.logger.info "A new rule file has been initialized: #{path}."
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 rule_template
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, rule_template.yaml)
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[:result, :try]
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 an overwrite the rule"
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).bind(check_nil_l)
25
+ result = builder.result.bind(check_diff_l).bind(update_and_run_l)
27
26
 
28
- if result.success?
29
- alert = result.value!
30
- data = Mihari::Entities::Alert.represent(alert)
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
- runner = Services::RuleRunner.new(rule, force_overwrite: force_overwrite)
55
- message = "There is a diff in the rule (#{rule.id}). Are you sure you want to overwrite the rule? (y/n)"
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)
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Mihari
4
4
  # @return [Array<String>]
5
- DEFAULT_DATA_TYPES = %w[hash ip domain url mail].freeze
5
+ DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
6
6
 
7
7
  # @return [Array<Hash>]
8
- DEFAULT_EMITTERS = %w[database misp slack the_hive].map { |name| { emitter: name } }.freeze
8
+ DEFAULT_EMITTERS = %w[database].map { |name| { emitter: name } }.freeze
9
9
 
10
10
  # @return [Array<Hash>]
11
- DEFAULT_ENRICHERS = %w[whois ipinfo shodan google_public_dns].map { |name| { enricher: name } }.freeze
11
+ DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
12
12
  end
@@ -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] **_options
20
+ # @param [Hash, nil] options
21
+ # @param [Hash] **_params
21
22
  #
22
- def initialize(artifacts:, rule:, **_options)
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] { run }.to_result
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 nil if artifacts.empty?
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) }