mihari 2.4.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/.overcommit.yml +12 -0
  4. data/README.md +1 -9
  5. data/build_frontend.sh +5 -0
  6. data/docker/Dockerfile +1 -1
  7. data/exe/mihari +1 -1
  8. data/lib/mihari.rb +89 -15
  9. data/lib/mihari/analyzers/base.rb +49 -8
  10. data/lib/mihari/analyzers/basic.rb +1 -2
  11. data/lib/mihari/analyzers/binaryedge.rb +7 -13
  12. data/lib/mihari/analyzers/censys.rb +26 -63
  13. data/lib/mihari/analyzers/circl.rb +20 -17
  14. data/lib/mihari/analyzers/crtsh.rb +6 -13
  15. data/lib/mihari/analyzers/dnpedia.rb +6 -12
  16. data/lib/mihari/analyzers/dnstwister.rb +13 -10
  17. data/lib/mihari/analyzers/onyphe.rb +6 -12
  18. data/lib/mihari/analyzers/otx.rb +22 -19
  19. data/lib/mihari/analyzers/passivetotal.rb +22 -21
  20. data/lib/mihari/analyzers/pulsedive.rb +16 -13
  21. data/lib/mihari/analyzers/rule.rb +97 -0
  22. data/lib/mihari/analyzers/securitytrails.rb +22 -19
  23. data/lib/mihari/analyzers/shodan.rb +7 -13
  24. data/lib/mihari/analyzers/spyse.rb +12 -19
  25. data/lib/mihari/analyzers/urlscan.rb +22 -27
  26. data/lib/mihari/analyzers/virustotal.rb +25 -22
  27. data/lib/mihari/analyzers/zoomeye.rb +14 -20
  28. data/lib/mihari/cli/analyzer.rb +44 -0
  29. data/lib/mihari/cli/base.rb +27 -0
  30. data/lib/mihari/cli/init.rb +13 -0
  31. data/lib/mihari/cli/main.rb +30 -0
  32. data/lib/mihari/cli/mixins/utils.rb +88 -0
  33. data/lib/mihari/cli/validator.rb +11 -0
  34. data/lib/mihari/commands/binaryedge.rb +1 -1
  35. data/lib/mihari/commands/censys.rb +1 -1
  36. data/lib/mihari/commands/circl.rb +2 -2
  37. data/lib/mihari/commands/crtsh.rb +1 -1
  38. data/lib/mihari/commands/dnpedia.rb +1 -1
  39. data/lib/mihari/commands/dnstwister.rb +2 -2
  40. data/lib/mihari/commands/init.rb +46 -0
  41. data/lib/mihari/commands/json.rb +1 -1
  42. data/lib/mihari/commands/onyphe.rb +1 -1
  43. data/lib/mihari/commands/otx.rb +2 -2
  44. data/lib/mihari/commands/passivetotal.rb +2 -2
  45. data/lib/mihari/commands/pulsedive.rb +2 -2
  46. data/lib/mihari/commands/search.rb +77 -0
  47. data/lib/mihari/commands/securitytrails.rb +2 -2
  48. data/lib/mihari/commands/shodan.rb +1 -1
  49. data/lib/mihari/commands/spyse.rb +1 -1
  50. data/lib/mihari/commands/urlscan.rb +2 -2
  51. data/lib/mihari/commands/validator.rb +38 -0
  52. data/lib/mihari/commands/virustotal.rb +2 -2
  53. data/lib/mihari/commands/zoomeye.rb +1 -1
  54. data/lib/mihari/constraints.rb +5 -0
  55. data/lib/mihari/database.rb +13 -2
  56. data/lib/mihari/emitters/base.rb +2 -2
  57. data/lib/mihari/emitters/database.rb +1 -1
  58. data/lib/mihari/emitters/misp.rb +3 -1
  59. data/lib/mihari/emitters/slack.rb +6 -7
  60. data/lib/mihari/emitters/the_hive.rb +1 -1
  61. data/lib/mihari/emitters/webhook.rb +2 -9
  62. data/lib/mihari/mixins/configurable.rb +38 -0
  63. data/lib/mihari/mixins/configuration.rb +90 -0
  64. data/lib/mihari/mixins/hash.rb +20 -0
  65. data/lib/mihari/mixins/refang.rb +21 -0
  66. data/lib/mihari/mixins/retriable.rb +27 -0
  67. data/lib/mihari/mixins/rule.rb +79 -0
  68. data/lib/mihari/models/alert.rb +28 -1
  69. data/lib/mihari/models/artifact.rb +11 -1
  70. data/lib/mihari/notifiers/base.rb +9 -1
  71. data/lib/mihari/notifiers/exception_notifier.rb +50 -0
  72. data/lib/mihari/notifiers/slack.rb +29 -0
  73. data/lib/mihari/schemas/analyzer.rb +25 -0
  74. data/lib/mihari/schemas/configuration.rb +42 -0
  75. data/lib/mihari/schemas/macros.rb +17 -0
  76. data/lib/mihari/schemas/rule.rb +72 -0
  77. data/lib/mihari/serializers/artifact.rb +1 -1
  78. data/lib/mihari/status.rb +14 -0
  79. data/lib/mihari/templates/rule.yml.erb +19 -0
  80. data/lib/mihari/type_checker.rb +8 -3
  81. data/lib/mihari/version.rb +1 -1
  82. data/lib/mihari/web/app.rb +2 -1
  83. data/lib/mihari/web/controllers/analyzers_controller.rb +38 -0
  84. data/lib/mihari/web/controllers/base_controller.rb +1 -1
  85. data/lib/mihari/web/public/index.html +1 -21
  86. data/lib/mihari/web/public/redoc-static.html +338 -461
  87. data/lib/mihari/web/public/static/js/app.365f1907.js +13 -0
  88. data/lib/mihari/web/public/static/js/app.365f1907.js.map +1 -0
  89. data/lib/mihari/web/public/static/js/app.ab213f7c.js +12 -0
  90. data/lib/mihari/web/public/static/js/app.ab213f7c.js.map +1 -0
  91. data/mihari.gemspec +16 -9
  92. metadata +135 -58
  93. data/.rubocop.yml +0 -161
  94. data/lib/mihari/analyzers/free_text.rb +0 -48
  95. data/lib/mihari/analyzers/http_hash.rb +0 -100
  96. data/lib/mihari/analyzers/passive_dns.rb +0 -59
  97. data/lib/mihari/analyzers/passive_ssl.rb +0 -55
  98. data/lib/mihari/analyzers/reverse_whois.rb +0 -55
  99. data/lib/mihari/analyzers/securitytrails_domain_feed.rb +0 -59
  100. data/lib/mihari/analyzers/ssh_fingerprint.rb +0 -58
  101. data/lib/mihari/cli.rb +0 -126
  102. data/lib/mihari/commands/config.rb +0 -27
  103. data/lib/mihari/commands/free_text.rb +0 -21
  104. data/lib/mihari/commands/http_hash.rb +0 -25
  105. data/lib/mihari/commands/passive_dns.rb +0 -21
  106. data/lib/mihari/commands/passive_ssl.rb +0 -21
  107. data/lib/mihari/commands/reverse_whois.rb +0 -21
  108. data/lib/mihari/commands/securitytrails_domain_feed.rb +0 -23
  109. data/lib/mihari/commands/ssh_fingerprint.rb +0 -21
  110. data/lib/mihari/config.rb +0 -85
  111. data/lib/mihari/configurable.rb +0 -21
  112. data/lib/mihari/html.rb +0 -43
  113. data/lib/mihari/retriable.rb +0 -17
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securitytrails"
4
-
5
- module Mihari
6
- module Analyzers
7
- class SecurityTrailsDomainFeed < Base
8
- attr_reader :type, :title, :description, :tags
9
-
10
- def initialize(regexp, type: "registered", title: nil, description: nil, tags: [])
11
- super()
12
-
13
- @_regexp = regexp
14
- @type = type
15
-
16
- raise InvalidInputError, "#{@_regexp} is not a valid regexp" unless regexp
17
- raise InvalidInputError, "#{type} is not a valid type" unless valid_type?
18
-
19
- @title = title || "SecurityTrails domain feed lookup"
20
- @description = description || "Regexp = /#{@_regexp}/"
21
- @tags = tags
22
- end
23
-
24
- def artifacts
25
- lookup || []
26
- end
27
-
28
- private
29
-
30
- def config_keys
31
- %w[securitytrails_api_key]
32
- end
33
-
34
- def api
35
- @api ||= ::SecurityTrails::API.new(Mihari.config.securitytrails_api_key)
36
- end
37
-
38
- def valid_type?
39
- %w[all new registered].include? type
40
- end
41
-
42
- def regexp
43
- @regexp ||= Regexp.compile(@_regexp)
44
- rescue InvalidInputError => _e
45
- nil
46
- end
47
-
48
- def lookup
49
- new_domains.select do |domain|
50
- regexp.match? domain
51
- end
52
- end
53
-
54
- def new_domains
55
- api.feeds.domains type
56
- end
57
- end
58
- end
59
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "parallel"
4
-
5
- module Mihari
6
- module Analyzers
7
- class SSHFingerprint < Base
8
- attr_reader :fingerprint, :title, :description, :tags
9
-
10
- def initialize(fingerprint, title: nil, description: nil, tags: [])
11
- super()
12
-
13
- @fingerprint = fingerprint
14
-
15
- @title = title || "SSH fingerprint cross search"
16
- @description = description || "fingerprint = #{fingerprint}"
17
- @tags = tags
18
- end
19
-
20
- def artifacts
21
- Parallel.map(analyzers) do |analyzer|
22
- run_analyzer analyzer
23
- end.flatten
24
- end
25
-
26
- private
27
-
28
- def valid_fingerprint?
29
- /^([0-9a-f]{2}:){15}[0-9a-f]{2}$/.match? fingerprint
30
- end
31
-
32
- def binary_edge
33
- BinaryEdge.new "ssh.fingerprint:\"#{fingerprint}\""
34
- end
35
-
36
- def shodan
37
- Shodan.new fingerprint
38
- end
39
-
40
- def analyzers
41
- raise InvalidInputError, "Invalid fingerprint is given." unless valid_fingerprint?
42
-
43
- [
44
- binary_edge,
45
- shodan
46
- ].compact
47
- end
48
-
49
- def run_analyzer(analyzer)
50
- analyzer.artifacts
51
- rescue ArgumentError, InvalidInputError => _e
52
- nil
53
- rescue ::BinaryEdge::Error, ::Shodan::Error => _e
54
- nil
55
- end
56
- end
57
- end
58
- end
data/lib/mihari/cli.rb DELETED
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "thor"
4
-
5
- require "mihari/commands/binaryedge"
6
- require "mihari/commands/censys"
7
- require "mihari/commands/circl"
8
- require "mihari/commands/crtsh"
9
- require "mihari/commands/dnpedia"
10
- require "mihari/commands/dnstwister"
11
- require "mihari/commands/onyphe"
12
- require "mihari/commands/otx"
13
- require "mihari/commands/passivetotal"
14
- require "mihari/commands/pulsedive"
15
- require "mihari/commands/securitytrails_domain_feed"
16
- require "mihari/commands/securitytrails"
17
- require "mihari/commands/shodan"
18
- require "mihari/commands/spyse"
19
- require "mihari/commands/urlscan"
20
- require "mihari/commands/virustotal"
21
- require "mihari/commands/zoomeye"
22
-
23
- require "mihari/commands/free_text"
24
- require "mihari/commands/http_hash"
25
- require "mihari/commands/passive_dns"
26
- require "mihari/commands/passive_ssl"
27
- require "mihari/commands/reverse_whois"
28
- require "mihari/commands/ssh_fingerprint"
29
-
30
- require "mihari/commands/config"
31
- require "mihari/commands/json"
32
- require "mihari/commands/web"
33
-
34
- module Mihari
35
- class CLI < Thor
36
- class_option :config, type: :string, desc: "Path to the config file"
37
-
38
- class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not. Only affects with analyze commands."
39
- class_option :ignore_threshold, type: :numeric, default: 0, desc: "Number of days to define whether an artifact is old or not. Only affects with analyze commands."
40
-
41
- include Mihari::Commands::BinaryEdge
42
- include Mihari::Commands::Censys
43
- include Mihari::Commands::CIRCL
44
- include Mihari::Commands::Config
45
- include Mihari::Commands::Crtsh
46
- include Mihari::Commands::DNPedia
47
- include Mihari::Commands::DNSTwister
48
- include Mihari::Commands::FreeText
49
- include Mihari::Commands::HTTPHash
50
- include Mihari::Commands::JSON
51
- include Mihari::Commands::Onyphe
52
- include Mihari::Commands::OTX
53
- include Mihari::Commands::PassiveDNS
54
- include Mihari::Commands::PassiveSSL
55
- include Mihari::Commands::PassiveTotal
56
- include Mihari::Commands::Pulsedive
57
- include Mihari::Commands::ReverseWhois
58
- include Mihari::Commands::SecurityTrails
59
- include Mihari::Commands::SecurityTrailsDomainFeed
60
- include Mihari::Commands::Shodan
61
- include Mihari::Commands::Spyse
62
- include Mihari::Commands::SSHFingerprint
63
- include Mihari::Commands::Urlscan
64
- include Mihari::Commands::VirusTotal
65
- include Mihari::Commands::Web
66
- include Mihari::Commands::ZoomEye
67
-
68
- class << self
69
- def exit_on_failure?
70
- true
71
- end
72
- end
73
-
74
- no_commands do
75
- def with_error_handling
76
- yield
77
- rescue StandardError => e
78
- notifier = Notifiers::ExceptionNotifier.new
79
- notifier.notify e
80
- end
81
-
82
- # @return [true, false]
83
- def valid_json?(json)
84
- %w[title description artifacts].all? { |key| json.key? key }
85
- end
86
-
87
- def load_configuration
88
- config = options["config"]
89
- return unless config
90
-
91
- Config.load_from_yaml(config)
92
- Database.connect
93
- end
94
-
95
- def run_analyzer(analyzer_class, query:, options:)
96
- load_configuration
97
-
98
- options = symbolize_hash_keys(options)
99
- options = normalize_options(options)
100
-
101
- analyzer = analyzer_class.new(query, **options)
102
-
103
- analyzer.ignore_old_artifacts = options[:ignore_old_artifacts] || false
104
- analyzer.ignore_threshold = options[:ignore_threshold] || 0
105
-
106
- analyzer.run
107
- end
108
-
109
- def symbolize_hash_keys(hash)
110
- hash.transform_keys(&:to_sym)
111
- end
112
-
113
- def normalize_options(options)
114
- # Delete :config because it is not intended to use for running an analyzer
115
- [:config, :ignore_old_artifacts, :ignore_threshold].each do |ignore_key|
116
- options.delete(ignore_key)
117
- end
118
- options
119
- end
120
-
121
- def refang(indicator)
122
- indicator.gsub("[.]", ".").gsub("(.)", ".")
123
- end
124
- end
125
- end
126
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "colorize"
4
-
5
- module Mihari
6
- module Commands
7
- module Config
8
- def self.included(thor)
9
- thor.class_eval do
10
- desc "init_config", "Create a config file"
11
- method_option :filename, type: :string, default: "mihari.yml"
12
- def init_config
13
- filename = options["filename"]
14
-
15
- warning = "#{filename} exists. Do you want to overwrite it? (y/n)"
16
- if File.exist?(filename) && !(yes? warning)
17
- return
18
- end
19
-
20
- Mihari::Config.initialize_yaml filename
21
- puts "The config file is initialized as #{filename}.".colorize(:blue)
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module FreeText
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "free_text [TEXT]", "Cross search with search engines by a free text"
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 free_text(text)
13
- with_error_handling do
14
- run_analyzer Analyzers::FreeText, query: text, options: options
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module HTTPHash
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "http_hash", "Cross search with search engines by a hash of an HTTP response (SHA256, MD5 and MurmurHash3)"
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
- method_option :md5, type: :string, desc: "MD5 hash"
13
- method_option :sha256, type: :string, desc: "SHA256 hash"
14
- method_option :mmh3, type: :numeric, desc: "MurmurHash3 hash"
15
- method_option :html, type: :string, desc: "path to an HTML file"
16
- def http_hash
17
- with_error_handling do
18
- run_analyzer Analyzers::HTTPHash, query: nil, options: options
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module PassiveDNS
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "passive_dns [IP|DOMAIN]", "Cross search with passive DNS services by an ip or domain"
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 passive_dns(query)
13
- with_error_handling do
14
- run_analyzer Analyzers::PassiveDNS, query: refang(query), options: options
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module PassiveSSL
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "passive_ssl [SHA1]", "Cross search with passive SSL services by an SHA1 certificate fingerprint"
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 passive_ssl(query)
13
- with_error_handling do
14
- run_analyzer Analyzers::PassiveSSL, query: query, options: options
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module ReverseWhois
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "reverse_whois [EMAIL]", "Cross search with reverse whois services by an email"
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 reverse_whois(query)
13
- with_error_handling do
14
- run_analyzer Analyzers::ReveseWhois, query: refang(query), options: options
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module SecurityTrailsDomainFeed
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "securitytrails_domain_feed [REGEXP]", "SecurityTrails new domain feed search by a regexp"
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
- method_option :type, type: :string, default: "registered", desc: "A type of domain feed ('all', 'new' or 'registered')"
13
- def securitytrails_domain_feed(regexp)
14
- with_error_handling do
15
- run_analyzer Analyzers::SecurityTrailsDomainFeed, query: regexp, options: options
16
- end
17
- end
18
- map "st_domain_feed" => :securitytrails_domain_feedd
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mihari
4
- module Commands
5
- module SSHFingerprint
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "ssh_fingerprint [FINGERPRINT]", "Cross search with search engines by an SSH fingerprint (e.g. dc:14:de:8e:d7:c1:15:43:23:82:25:81:d2:59:e8:c0)"
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 ssh_fingerprint(fingerprint)
13
- with_error_handling do
14
- run_analyzer Analyzers::SSHFingerprint, query: fingerprint, options: options
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
data/lib/mihari/config.rb DELETED
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- module Mihari
6
- class Config
7
- attr_accessor :binaryedge_api_key, :censys_id, :censys_secret, :circl_passive_password, :circl_passive_username, :misp_api_endpoint, :misp_api_key, :onyphe_api_key, :otx_api_key, :passivetotal_api_key, :passivetotal_username, :pulsedive_api_key, :securitytrails_api_key, :shodan_api_key, :slack_channel, :slack_webhook_url, :spyse_api_key, :thehive_api_endpoint, :thehive_api_key, :urlscan_api_key, :virustotal_api_key, :zoomeye_api_key, :webhook_url, :webhook_use_json_body, :database
8
-
9
- def initialize
10
- load_from_env
11
- end
12
-
13
- def load_from_env
14
- @binaryedge_api_key = ENV["BINARYEDGE_API_KEY"]
15
- @censys_id = ENV["CENSYS_ID"]
16
- @censys_secret = ENV["CENSYS_SECRET"]
17
- @circl_passive_password = ENV["CIRCL_PASSIVE_PASSWORD"]
18
- @circl_passive_username = ENV["CIRCL_PASSIVE_USERNAME"]
19
- @misp_api_endpoint = ENV["MISP_API_ENDPOINT"]
20
- @misp_api_key = ENV["MISP_API_KEY"]
21
- @onyphe_api_key = ENV["ONYPHE_API_KEY"]
22
- @otx_api_key = ENV["OTX_API_KEY"]
23
- @passivetotal_api_key = ENV["PASSIVETOTAL_API_KEY"]
24
- @passivetotal_username = ENV["PASSIVETOTAL_USERNAME"]
25
- @pulsedive_api_key = ENV["PULSEDIVE_API_KEY"]
26
- @securitytrails_api_key = ENV["SECURITYTRAILS_API_KEY"]
27
- @shodan_api_key = ENV["SHODAN_API_KEY"]
28
- @slack_channel = ENV["SLACK_CHANNEL"]
29
- @slack_webhook_url = ENV["SLACK_WEBHOOK_URL"]
30
- @spyse_api_key = ENV["SPYSE_API_KEY"]
31
- @thehive_api_endpoint = ENV["THEHIVE_API_ENDPOINT"]
32
- @thehive_api_key = ENV["THEHIVE_API_KEY"]
33
- @urlscan_api_key = ENV["URLSCAN_API_KEY"]
34
- @virustotal_api_key = ENV["VIRUSTOTAL_API_KEY"]
35
- @zoomeye_api_key = ENV["ZOOMEYE_API_KEY"]
36
- @webhook_url = ENV["WEBHOOK_URL"]
37
- @webhook_use_json_body = ENV["WEBHOOK_USE_JSON_BODY"]
38
-
39
- @database = ENV["DATABASE"] || "mihari.db"
40
- end
41
-
42
- class << self
43
- def load_from_yaml(path)
44
- raise ArgumentError, "#{path} does not exist." unless File.exist?(path)
45
-
46
- data = File.read(path)
47
- begin
48
- yaml = YAML.safe_load(data)
49
- rescue TypeError => _e
50
- return
51
- end
52
-
53
- Mihari.configure do |config|
54
- yaml.each do |key, value|
55
- config.send("#{key.downcase}=".to_sym, value)
56
- end
57
- end
58
- end
59
-
60
- def initialize_yaml(filename)
61
- keys = new.instance_variables.map do |key|
62
- key.to_s[1..]
63
- end
64
-
65
- config = keys.map do |key|
66
- [key, nil]
67
- end.to_h
68
-
69
- YAML.dump(config, File.open(filename, "w"))
70
- end
71
- end
72
- end
73
-
74
- class << self
75
- def config
76
- @config ||= Config.new
77
- end
78
-
79
- attr_writer :config
80
-
81
- def configure
82
- yield config
83
- end
84
- end
85
- end