mihari 3.9.0 → 3.10.1
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 +6 -7
- data/README.md +1 -0
- data/config.ru +1 -1
- data/lib/mihari/analyzers/greynoise.rb +65 -0
- data/lib/mihari/analyzers/rule.rb +1 -0
- data/lib/mihari/analyzers/shodan.rb +11 -5
- data/lib/mihari/cli/analyzer.rb +2 -0
- data/lib/mihari/commands/greynoise.rb +21 -0
- data/lib/mihari/commands/search.rb +3 -2
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/mixins/configuration.rb +12 -2
- data/lib/mihari/models/alert.rb +1 -8
- data/lib/mihari/models/artifact.rb +3 -0
- data/lib/mihari/schemas/configuration.rb +3 -2
- data/lib/mihari/structs/greynoise.rb +55 -0
- data/lib/mihari/structs/ipinfo.rb +3 -4
- data/lib/mihari/structs/shodan.rb +6 -6
- data/lib/mihari/types.rb +1 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/api.rb +43 -0
- data/lib/mihari/web/app.rb +47 -29
- data/lib/mihari/web/endpoints/alerts.rb +74 -0
- data/lib/mihari/web/endpoints/artifacts.rb +92 -0
- data/lib/mihari/web/endpoints/command.rb +32 -0
- data/lib/mihari/web/endpoints/configs.rb +22 -0
- data/lib/mihari/web/endpoints/ip_addresses.rb +27 -0
- data/lib/mihari/web/endpoints/sources.rb +18 -0
- data/lib/mihari/web/endpoints/tags.rb +38 -0
- data/lib/mihari/web/entities/alert.rb +23 -0
- data/lib/mihari/web/entities/artifact.rb +24 -0
- data/lib/mihari/web/entities/autonomous_system.rb +9 -0
- data/lib/mihari/web/entities/command.rb +14 -0
- data/lib/mihari/web/entities/config.rb +16 -0
- data/lib/mihari/web/entities/dns.rb +10 -0
- data/lib/mihari/web/entities/geolocation.rb +10 -0
- data/lib/mihari/web/entities/ip_address.rb +13 -0
- data/lib/mihari/web/entities/message.rb +9 -0
- data/lib/mihari/web/entities/reverse_dns.rb +9 -0
- data/lib/mihari/web/entities/source.rb +9 -0
- data/lib/mihari/web/entities/tag.rb +13 -0
- data/lib/mihari/web/entities/whois.rb +16 -0
- data/lib/mihari/web/public/grape.rb +73 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +54 -28
- data/lib/mihari/web/public/static/js/app.0a0cc502.js +21 -0
- data/lib/mihari/web/public/static/js/app.0a0cc502.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.14008741.js +21 -0
- data/lib/mihari/web/public/static/js/app.14008741.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.6b636b62.js +50 -0
- data/lib/mihari/web/public/static/js/app.6b636b62.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.fbc19869.js +21 -0
- data/lib/mihari/web/public/static/js/app.fbc19869.js.map +1 -0
- data/lib/mihari.rb +7 -14
- data/mihari.gemspec +9 -5
- data/sig/lib/mihari/structs/greynoise.rbs +30 -0
- data/sig/lib/mihari/structs/shodan.rbs +3 -3
- data/sig/lib/mihari/web/app.rbs +1 -1
- metadata +146 -74
- data/lib/mihari/serializers/alert.rb +0 -14
- data/lib/mihari/serializers/artifact.rb +0 -18
- data/lib/mihari/serializers/autonomous_system.rb +0 -9
- data/lib/mihari/serializers/dns.rb +0 -11
- data/lib/mihari/serializers/geolocation.rb +0 -11
- data/lib/mihari/serializers/reverse_dns.rb +0 -11
- data/lib/mihari/serializers/tag.rb +0 -11
- data/lib/mihari/serializers/whois.rb +0 -11
- data/lib/mihari/web/controllers/alerts_controller.rb +0 -67
- data/lib/mihari/web/controllers/analyzers_controller.rb +0 -38
- data/lib/mihari/web/controllers/artifacts_controller.rb +0 -94
- data/lib/mihari/web/controllers/base_controller.rb +0 -22
- data/lib/mihari/web/controllers/command_controller.rb +0 -26
- data/lib/mihari/web/controllers/config_controller.rb +0 -13
- data/lib/mihari/web/controllers/ip_address_controller.rb +0 -21
- data/lib/mihari/web/controllers/sources_controller.rb +0 -12
- data/lib/mihari/web/controllers/tags_controller.rb +0 -30
- data/lib/mihari/web/helpers/json.rb +0 -53
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fcea779559f641125a388f63f0aa2d39979842f9b66f116194c36fe6410c56c
|
|
4
|
+
data.tar.gz: ceefe0fa86e5d3c8066bab202f7dc1dfb2b21017554a2b63c5a18f567e45aadb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 834c4c815ddf9c14b9047c9a8d9ed8ce56e3f115c16b797c4ebf40205529f7c94fe7ddda8f9c7f5cc0675c65123ac0957d28a0bfe51108d2492efc230f509dd8
|
|
7
|
+
data.tar.gz: 03ed05a672e17bfce72706de94aa2a339053f9b6545907c1e71cf60987be047e1af6bc6d4e95a16f6b6bb371de2ff3b65602ae4c3c9eb2eab3f70c2ace0735cf
|
data/.github/workflows/test.yml
CHANGED
|
@@ -43,17 +43,16 @@ jobs:
|
|
|
43
43
|
|
|
44
44
|
steps:
|
|
45
45
|
- uses: actions/checkout@v2
|
|
46
|
-
- name: Set up Ruby 2.7
|
|
47
|
-
uses: ruby/setup-ruby@v1
|
|
48
|
-
with:
|
|
49
|
-
ruby-version: ${{ matrix.ruby }}
|
|
50
|
-
bundler-cache: true
|
|
51
46
|
|
|
52
47
|
- name: Install dependencies
|
|
53
48
|
run: |
|
|
54
49
|
sudo apt-get -yqq install libpq-dev libmysqlclient-dev
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
|
|
51
|
+
- name: Set up Ruby
|
|
52
|
+
uses: ruby/setup-ruby@v1
|
|
53
|
+
with:
|
|
54
|
+
ruby-version: ${{ matrix.ruby }}
|
|
55
|
+
bundler-cache: true
|
|
57
56
|
|
|
58
57
|
- name: Test with PostgreSQL
|
|
59
58
|
env:
|
data/README.md
CHANGED
|
@@ -38,6 +38,7 @@ Mihari supports the following services by default.
|
|
|
38
38
|
- [crt.sh](https://crt.sh/)
|
|
39
39
|
- [DN Pedia](https://dnpedia.com/)
|
|
40
40
|
- [dnstwister](https://dnstwister.report/)
|
|
41
|
+
- [GreyNoise](https://www.greynoise.io/)
|
|
41
42
|
- [Onyphe](https://onyphe.io)
|
|
42
43
|
- [OTX](https://otx.alienvault.com/)
|
|
43
44
|
- [PassiveTotal](https://community.riskiq.com/)
|
data/config.ru
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "greynoise"
|
|
4
|
+
|
|
5
|
+
module Mihari
|
|
6
|
+
module Analyzers
|
|
7
|
+
class GreyNoise < Base
|
|
8
|
+
param :query
|
|
9
|
+
option :title, default: proc { "GreyNoise search" }
|
|
10
|
+
option :description, default: proc { "query = #{query}" }
|
|
11
|
+
option :tags, default: proc { [] }
|
|
12
|
+
|
|
13
|
+
def artifacts
|
|
14
|
+
res = Structs::GreyNoise::Response.from_dynamic!(search)
|
|
15
|
+
res.data.map do |datum|
|
|
16
|
+
build_artifact datum
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
PAGE_SIZE = 10_000
|
|
23
|
+
|
|
24
|
+
def configuration_keys
|
|
25
|
+
%w[greynoise_api_key]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def api
|
|
29
|
+
@api ||= ::GreyNoise::API.new(key: Mihari.config.greynoise_api_key)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# Search
|
|
34
|
+
#
|
|
35
|
+
# @return [Hash]
|
|
36
|
+
#
|
|
37
|
+
def search
|
|
38
|
+
api.experimental.gnql(query, size: PAGE_SIZE)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Build an artifact from a GreyNoise search API response
|
|
43
|
+
#
|
|
44
|
+
# @param [Structs::GreyNoise::Datum] datum
|
|
45
|
+
#
|
|
46
|
+
# @return [Artifact]
|
|
47
|
+
#
|
|
48
|
+
def build_artifact(datum)
|
|
49
|
+
as = AutonomousSystem.new(asn: normalize_asn(datum.metadata.asn))
|
|
50
|
+
|
|
51
|
+
geolocation = Geolocation.new(
|
|
52
|
+
country: datum.metadata.country,
|
|
53
|
+
country_code: datum.metadata.country_code
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
Artifact.new(
|
|
57
|
+
data: datum.ip,
|
|
58
|
+
source: source,
|
|
59
|
+
autonomous_system: as,
|
|
60
|
+
geolocation: geolocation
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -58,6 +58,7 @@ module Mihari
|
|
|
58
58
|
responses = []
|
|
59
59
|
(1..Float::INFINITY).each do |page|
|
|
60
60
|
res = search_with_page(query, page: page)
|
|
61
|
+
|
|
61
62
|
break unless res
|
|
62
63
|
|
|
63
64
|
responses << res
|
|
@@ -78,11 +79,16 @@ module Mihari
|
|
|
78
79
|
# @return [Artifact]
|
|
79
80
|
#
|
|
80
81
|
def build_artifact(match)
|
|
81
|
-
as =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
as = nil
|
|
83
|
+
as = AutonomousSystem.new(asn: normalize_asn(match.asn)) unless match.asn.nil?
|
|
84
|
+
|
|
85
|
+
geolocation = nil
|
|
86
|
+
if !match.location.country_name.nil? && !match.location.country_code.nil?
|
|
87
|
+
geolocation = Geolocation.new(
|
|
88
|
+
country: match.location.country_name,
|
|
89
|
+
country_code: match.location.country_code
|
|
90
|
+
)
|
|
91
|
+
end
|
|
86
92
|
|
|
87
93
|
Artifact.new(
|
|
88
94
|
data: match.ip_str,
|
data/lib/mihari/cli/analyzer.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "mihari/commands/circl"
|
|
|
6
6
|
require "mihari/commands/crtsh"
|
|
7
7
|
require "mihari/commands/dnpedia"
|
|
8
8
|
require "mihari/commands/dnstwister"
|
|
9
|
+
require "mihari/commands/greynoise"
|
|
9
10
|
require "mihari/commands/onyphe"
|
|
10
11
|
require "mihari/commands/otx"
|
|
11
12
|
require "mihari/commands/passivetotal"
|
|
@@ -33,6 +34,7 @@ module Mihari
|
|
|
33
34
|
include Mihari::Commands::Crtsh
|
|
34
35
|
include Mihari::Commands::DNPedia
|
|
35
36
|
include Mihari::Commands::DNSTwister
|
|
37
|
+
include Mihari::Commands::GreyNoise
|
|
36
38
|
include Mihari::Commands::JSON
|
|
37
39
|
include Mihari::Commands::Onyphe
|
|
38
40
|
include Mihari::Commands::OTX
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Commands
|
|
5
|
+
module GreyNoise
|
|
6
|
+
def self.included(thor)
|
|
7
|
+
thor.class_eval do
|
|
8
|
+
desc "greynoise [QUERY]", "GreyNoise search"
|
|
9
|
+
method_option :title, type: :string, desc: "title"
|
|
10
|
+
method_option :description, type: :string, desc: "description"
|
|
11
|
+
method_option :tags, type: :array, desc: "tags"
|
|
12
|
+
def greynoise(query)
|
|
13
|
+
with_error_handling do
|
|
14
|
+
run_analyzer Analyzers::GreyNoise, query: query, options: options
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -10,6 +10,9 @@ module Mihari
|
|
|
10
10
|
desc "search [RULE]", "Search by a rule"
|
|
11
11
|
method_option :config, type: :string, desc: "Path to the config file"
|
|
12
12
|
def search_by_rule(rule)
|
|
13
|
+
# load configuration
|
|
14
|
+
load_configuration
|
|
15
|
+
|
|
13
16
|
# convert str(YAML) to hash or str(path/YAML file) to hash
|
|
14
17
|
rule = load_rule(rule)
|
|
15
18
|
|
|
@@ -77,8 +80,6 @@ module Mihari
|
|
|
77
80
|
# @return [nil]
|
|
78
81
|
#
|
|
79
82
|
def run_rule_analyzer(analyzer, ignore_old_artifacts: false, ignore_threshold: 0)
|
|
80
|
-
load_configuration
|
|
81
|
-
|
|
82
83
|
analyzer.ignore_old_artifacts = ignore_old_artifacts
|
|
83
84
|
analyzer.ignore_threshold = ignore_threshold
|
|
84
85
|
|
data/lib/mihari/errors.rb
CHANGED
|
@@ -80,10 +80,20 @@ module Mihari
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
#
|
|
84
|
+
# Load configuration file
|
|
85
|
+
#
|
|
86
|
+
# @param [String] path
|
|
87
|
+
#
|
|
88
|
+
# @return [Hash]
|
|
89
|
+
#
|
|
83
90
|
def _load_config(path)
|
|
84
|
-
|
|
91
|
+
unless Pathname(path).exist?
|
|
92
|
+
puts "#{path} does not exist".colorize(:red)
|
|
93
|
+
raise FileNotFoundError
|
|
94
|
+
end
|
|
85
95
|
|
|
86
|
-
YAML.safe_load(path, symbolize_names: true)
|
|
96
|
+
YAML.safe_load(File.read(path), symbolize_names: true)
|
|
87
97
|
end
|
|
88
98
|
end
|
|
89
99
|
end
|
data/lib/mihari/models/alert.rb
CHANGED
|
@@ -30,14 +30,7 @@ module Mihari
|
|
|
30
30
|
|
|
31
31
|
# TODO: improve queires
|
|
32
32
|
alert_ids = relation.limit(limit).offset(offset).order(id: :desc).pluck(:id).uniq
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
alerts.map do |alert|
|
|
36
|
-
json = Serializers::AlertSerializer.new(alert).as_json
|
|
37
|
-
json[:artifacts] = json[:artifacts] || []
|
|
38
|
-
json[:tags] = json[:tags] || []
|
|
39
|
-
json
|
|
40
|
-
end
|
|
33
|
+
includes(:artifacts, :tags).where(id: [alert_ids]).order(id: :desc)
|
|
41
34
|
end
|
|
42
35
|
|
|
43
36
|
#
|
|
@@ -13,6 +13,8 @@ module Mihari
|
|
|
13
13
|
optional(:censys_secret).value(:string)
|
|
14
14
|
optional(:circl_passive_password).value(:string)
|
|
15
15
|
optional(:circl_passive_username).value(:string)
|
|
16
|
+
optional(:database).value(:string)
|
|
17
|
+
optional(:greynoise_api_key).value(:string)
|
|
16
18
|
optional(:ipinfo_api_key).value(:string)
|
|
17
19
|
optional(:misp_api_endpoint).value(:string)
|
|
18
20
|
optional(:misp_api_key).value(:string)
|
|
@@ -30,10 +32,9 @@ module Mihari
|
|
|
30
32
|
optional(:thehive_api_key).value(:string)
|
|
31
33
|
optional(:urlscan_api_key).value(:string)
|
|
32
34
|
optional(:virustotal_api_key).value(:string)
|
|
33
|
-
optional(:zoomeye_api_key).value(:string)
|
|
34
35
|
optional(:webhook_url).value(:string)
|
|
35
36
|
optional(:webhook_use_json_body).value(:bool)
|
|
36
|
-
optional(:
|
|
37
|
+
optional(:zoomeye_api_key).value(:string)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
class ConfigurationContract < Dry::Validation::Contract
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "dry/struct"
|
|
3
|
+
|
|
4
|
+
module Mihari
|
|
5
|
+
module Structs
|
|
6
|
+
module GreyNoise
|
|
7
|
+
class Metadata < Dry::Struct
|
|
8
|
+
attribute :country, Types::String
|
|
9
|
+
attribute :country_code, Types::String
|
|
10
|
+
attribute :asn, Types::String
|
|
11
|
+
|
|
12
|
+
def self.from_dynamic!(d)
|
|
13
|
+
d = Types::Hash[d]
|
|
14
|
+
new(
|
|
15
|
+
country: d.fetch("country"),
|
|
16
|
+
country_code: d.fetch("country_code"),
|
|
17
|
+
asn: d.fetch("asn")
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Datum < Dry::Struct
|
|
23
|
+
attribute :ip, Types::String
|
|
24
|
+
attribute :metadata, Metadata
|
|
25
|
+
|
|
26
|
+
def self.from_dynamic!(d)
|
|
27
|
+
d = Types::Hash[d]
|
|
28
|
+
new(
|
|
29
|
+
ip: d.fetch("ip"),
|
|
30
|
+
metadata: Metadata.from_dynamic!(d.fetch("metadata"))
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Response < Dry::Struct
|
|
36
|
+
attribute :complete, Types::Bool
|
|
37
|
+
attribute :count, Types::Int
|
|
38
|
+
attribute :data, Types.Array(Datum)
|
|
39
|
+
attribute :message, Types::String
|
|
40
|
+
attribute :query, Types::String
|
|
41
|
+
|
|
42
|
+
def self.from_dynamic!(d)
|
|
43
|
+
d = Types::Hash[d]
|
|
44
|
+
new(
|
|
45
|
+
complete: d.fetch("complete"),
|
|
46
|
+
count: d.fetch("count"),
|
|
47
|
+
data: d.fetch("data").map { |x| Datum.from_dynamic!(x) },
|
|
48
|
+
message: d.fetch("message"),
|
|
49
|
+
query: d.fetch("query")
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -5,20 +5,20 @@ module Mihari
|
|
|
5
5
|
module Structs
|
|
6
6
|
module Shodan
|
|
7
7
|
class Location < Dry::Struct
|
|
8
|
-
attribute :country_code, Types::String
|
|
9
|
-
attribute :country_name, Types::String
|
|
8
|
+
attribute :country_code, Types::String.optional
|
|
9
|
+
attribute :country_name, Types::String.optional
|
|
10
10
|
|
|
11
11
|
def self.from_dynamic!(d)
|
|
12
12
|
d = Types::Hash[d]
|
|
13
13
|
new(
|
|
14
|
-
country_code: d
|
|
15
|
-
country_name: d
|
|
14
|
+
country_code: d["country_code"],
|
|
15
|
+
country_name: d["country_name"]
|
|
16
16
|
)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
class Match < Dry::Struct
|
|
21
|
-
attribute :asn, Types::String
|
|
21
|
+
attribute :asn, Types::String.optional
|
|
22
22
|
attribute :hostnames, Types.Array(Types::String)
|
|
23
23
|
attribute :location, Location
|
|
24
24
|
attribute :domains, Types.Array(Types::String)
|
|
@@ -27,7 +27,7 @@ module Mihari
|
|
|
27
27
|
def self.from_dynamic!(d)
|
|
28
28
|
d = Types::Hash[d]
|
|
29
29
|
new(
|
|
30
|
-
asn: d
|
|
30
|
+
asn: d["asn"],
|
|
31
31
|
hostnames: d.fetch("hostnames"),
|
|
32
32
|
location: Location.from_dynamic!(d.fetch("location")),
|
|
33
33
|
domains: d.fetch("domains"),
|
data/lib/mihari/types.rb
CHANGED
data/lib/mihari/version.rb
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Entities
|
|
2
|
+
require "mihari/web/entities/message"
|
|
3
|
+
|
|
4
|
+
require "mihari/web/entities/autonomous_system"
|
|
5
|
+
require "mihari/web/entities/command"
|
|
6
|
+
require "mihari/web/entities/config"
|
|
7
|
+
require "mihari/web/entities/dns"
|
|
8
|
+
require "mihari/web/entities/geolocation"
|
|
9
|
+
require "mihari/web/entities/ip_address"
|
|
10
|
+
require "mihari/web/entities/reverse_dns"
|
|
11
|
+
require "mihari/web/entities/source"
|
|
12
|
+
require "mihari/web/entities/tag"
|
|
13
|
+
require "mihari/web/entities/whois"
|
|
14
|
+
|
|
15
|
+
require "mihari/web/entities/artifact"
|
|
16
|
+
|
|
17
|
+
require "mihari/web/entities/alert"
|
|
18
|
+
|
|
19
|
+
# Endpoints
|
|
20
|
+
require "mihari/web/endpoints/alerts"
|
|
21
|
+
require "mihari/web/endpoints/artifacts"
|
|
22
|
+
require "mihari/web/endpoints/command"
|
|
23
|
+
require "mihari/web/endpoints/configs"
|
|
24
|
+
require "mihari/web/endpoints/ip_addresses"
|
|
25
|
+
require "mihari/web/endpoints/sources"
|
|
26
|
+
require "mihari/web/endpoints/tags"
|
|
27
|
+
|
|
28
|
+
module Mihari
|
|
29
|
+
class API < Grape::API
|
|
30
|
+
prefix "api"
|
|
31
|
+
format :json
|
|
32
|
+
|
|
33
|
+
mount Endpoints::Alerts
|
|
34
|
+
mount Endpoints::Artifacts
|
|
35
|
+
mount Endpoints::Command
|
|
36
|
+
mount Endpoints::Configs
|
|
37
|
+
mount Endpoints::IPAddresses
|
|
38
|
+
mount Endpoints::Sources
|
|
39
|
+
mount Endpoints::Tags
|
|
40
|
+
|
|
41
|
+
add_swagger_documentation(api_version: "v1", info: { title: "Mihari API" })
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/mihari/web/app.rb
CHANGED
|
@@ -2,46 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
require "launchy"
|
|
4
4
|
require "rack"
|
|
5
|
+
require "rack/contrib"
|
|
5
6
|
require "rack/handler/puma"
|
|
6
|
-
require "
|
|
7
|
+
require "rack/cors"
|
|
7
8
|
|
|
8
|
-
require "
|
|
9
|
+
require "grape"
|
|
10
|
+
require "grape-entity"
|
|
11
|
+
require "grape-swagger"
|
|
12
|
+
require "grape-swagger-entity"
|
|
9
13
|
|
|
10
|
-
require "mihari/web/
|
|
11
|
-
|
|
12
|
-
require "mihari/web/controllers/alerts_controller"
|
|
13
|
-
require "mihari/web/controllers/analyzers_controller"
|
|
14
|
-
require "mihari/web/controllers/artifacts_controller"
|
|
15
|
-
require "mihari/web/controllers/command_controller"
|
|
16
|
-
require "mihari/web/controllers/config_controller"
|
|
17
|
-
require "mihari/web/controllers/ip_address_controller"
|
|
18
|
-
require "mihari/web/controllers/sources_controller"
|
|
19
|
-
require "mihari/web/controllers/tags_controller"
|
|
14
|
+
require "mihari/web/api"
|
|
20
15
|
|
|
21
16
|
module Mihari
|
|
22
|
-
class App
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
class App
|
|
18
|
+
def initialize
|
|
19
|
+
@filenames = ["", ".html", "index.html", "/index.html"]
|
|
20
|
+
@rack_static = ::Rack::Static.new(
|
|
21
|
+
-> { [404, {}, []] },
|
|
22
|
+
root: File.expand_path("./public", __dir__),
|
|
23
|
+
urls: ["/"]
|
|
24
|
+
)
|
|
28
25
|
end
|
|
29
26
|
|
|
30
|
-
use Mihari::Controllers::AlertsController
|
|
31
|
-
use Mihari::Controllers::AnalyzersController
|
|
32
|
-
use Mihari::Controllers::ArtifactsController
|
|
33
|
-
use Mihari::Controllers::CommandController
|
|
34
|
-
use Mihari::Controllers::ConfigController
|
|
35
|
-
use Mihari::Controllers::IPAddressController
|
|
36
|
-
use Mihari::Controllers::SourcesController
|
|
37
|
-
use Mihari::Controllers::TagsController
|
|
38
|
-
|
|
39
27
|
class << self
|
|
28
|
+
def instance
|
|
29
|
+
@instance ||= Rack::Builder.new do
|
|
30
|
+
use Rack::Cors do
|
|
31
|
+
allow do
|
|
32
|
+
origins "*"
|
|
33
|
+
resource "*", headers: :any, methods: [:get, :post, :put, :delete, :options]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
run App.new
|
|
38
|
+
end.to_app
|
|
39
|
+
end
|
|
40
|
+
|
|
40
41
|
def run!(port: 9292, host: "localhost", threads: "0:16", verbose: false)
|
|
41
42
|
url = "http://#{host}:#{port}"
|
|
42
43
|
|
|
43
|
-
Rack::Handler::Puma.run(
|
|
44
|
-
Launchy.open
|
|
44
|
+
Rack::Handler::Puma.run(instance, Port: port, Host: host, Threads: threads, Verbose: verbose) do |server|
|
|
45
|
+
Launchy.open(url) if ENV["RACK_ENV"] != "development"
|
|
45
46
|
|
|
46
47
|
[:INT, :TERM].each do |sig|
|
|
47
48
|
trap(sig) do
|
|
@@ -51,5 +52,22 @@ module Mihari
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
end
|
|
55
|
+
|
|
56
|
+
def call(env)
|
|
57
|
+
# api
|
|
58
|
+
api_response = API.call(env)
|
|
59
|
+
|
|
60
|
+
# Check if the App wants us to pass the response along to others
|
|
61
|
+
if api_response[1]["X-Cascade"] == "pass"
|
|
62
|
+
# static files
|
|
63
|
+
request_path = env["PATH_INFO"]
|
|
64
|
+
@filenames.each do |path|
|
|
65
|
+
response = @rack_static.call(env.merge("PATH_INFO" => request_path + path))
|
|
66
|
+
return response if response[0] != 404
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
api_response
|
|
71
|
+
end
|
|
54
72
|
end
|
|
55
73
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mihari
|
|
4
|
+
module Endpoints
|
|
5
|
+
class Alerts < Grape::API
|
|
6
|
+
namespace :alerts do
|
|
7
|
+
desc "Search alerts", {
|
|
8
|
+
is_array: true,
|
|
9
|
+
success: Entities::Alert,
|
|
10
|
+
failure: [{ code: 404, message: "Not found", model: Entities::Message }]
|
|
11
|
+
}
|
|
12
|
+
params do
|
|
13
|
+
optional :page, type: Integer
|
|
14
|
+
optional :artifact, type: String
|
|
15
|
+
optional :description, type: String
|
|
16
|
+
optional :source, type: String
|
|
17
|
+
optional :tag, type: String
|
|
18
|
+
|
|
19
|
+
optional :fromAt, type: DateTime
|
|
20
|
+
optional :toAt, type: DateTime
|
|
21
|
+
|
|
22
|
+
optional :asn, type: Integer
|
|
23
|
+
optional :dnsRecord, type: String
|
|
24
|
+
optional :reverseDnsName, type: String
|
|
25
|
+
end
|
|
26
|
+
get "/" do
|
|
27
|
+
filter = params.to_h.to_snake_keys
|
|
28
|
+
|
|
29
|
+
# set page & limit
|
|
30
|
+
page = filter["page"] || 1
|
|
31
|
+
filter["page"] = page.to_i
|
|
32
|
+
|
|
33
|
+
limit = 10
|
|
34
|
+
filter["limit"] = 10
|
|
35
|
+
|
|
36
|
+
# normalize keys
|
|
37
|
+
filter["artifact_data"] = filter["artifact"]
|
|
38
|
+
filter["tag_name"] = filter["tag"]
|
|
39
|
+
|
|
40
|
+
# symbolize hash keys
|
|
41
|
+
filter = filter.to_h.transform_keys(&:to_sym)
|
|
42
|
+
|
|
43
|
+
search_filter_with_pagenation = Structs::Alert::SearchFilterWithPagination.new(**filter)
|
|
44
|
+
alerts = Mihari::Alert.search(search_filter_with_pagenation)
|
|
45
|
+
total = Mihari::Alert.count(search_filter_with_pagenation.without_pagination)
|
|
46
|
+
|
|
47
|
+
present({ alerts: alerts, total: total, current_page: page, page_size: limit }, with: Entities::AlertsWithPagination)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc "Delete an alert", {
|
|
51
|
+
success: Entities::Message,
|
|
52
|
+
failure: [{ code: 404, message: "Not found", model: Entities::Message }]
|
|
53
|
+
}
|
|
54
|
+
params do
|
|
55
|
+
requires :id, type: Integer
|
|
56
|
+
end
|
|
57
|
+
delete "/:id" do
|
|
58
|
+
id = params["id"].to_i
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
alert = Mihari::Alert.find(id)
|
|
62
|
+
rescue ActiveRecord::RecordNotFound
|
|
63
|
+
error!({ message: "ID:#{id} is not found" }, 404)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
alert.destroy
|
|
67
|
+
|
|
68
|
+
status 204
|
|
69
|
+
present({ message: "" }, with: Entities::Message)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|