threatinator 0.1.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 +7 -0
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +119 -0
- data/Gemfile +28 -0
- data/LICENSE +165 -0
- data/README.md +45 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/bin/threatinator +5 -0
- data/lib/threatinator.rb +3 -0
- data/lib/threatinator/action.rb +14 -0
- data/lib/threatinator/actions/list.rb +2 -0
- data/lib/threatinator/actions/list/action.rb +53 -0
- data/lib/threatinator/actions/list/config.rb +10 -0
- data/lib/threatinator/actions/run.rb +2 -0
- data/lib/threatinator/actions/run/action.rb +45 -0
- data/lib/threatinator/actions/run/config.rb +32 -0
- data/lib/threatinator/actions/run/coverage_observer.rb +54 -0
- data/lib/threatinator/actions/run/output_config.rb +59 -0
- data/lib/threatinator/cli.rb +13 -0
- data/lib/threatinator/cli/action_builder.rb +33 -0
- data/lib/threatinator/cli/list_action_builder.rb +19 -0
- data/lib/threatinator/cli/parser.rb +113 -0
- data/lib/threatinator/cli/run_action_builder.rb +41 -0
- data/lib/threatinator/config.rb +6 -0
- data/lib/threatinator/config/base.rb +35 -0
- data/lib/threatinator/config/feed_search.rb +25 -0
- data/lib/threatinator/decoder.rb +24 -0
- data/lib/threatinator/decoders/gzip.rb +30 -0
- data/lib/threatinator/event.rb +27 -0
- data/lib/threatinator/event_builder.rb +41 -0
- data/lib/threatinator/exceptions.rb +61 -0
- data/lib/threatinator/feed.rb +82 -0
- data/lib/threatinator/feed_builder.rb +156 -0
- data/lib/threatinator/feed_registry.rb +47 -0
- data/lib/threatinator/feed_runner.rb +118 -0
- data/lib/threatinator/fetcher.rb +22 -0
- data/lib/threatinator/fetchers/http.rb +46 -0
- data/lib/threatinator/filter.rb +12 -0
- data/lib/threatinator/filters/block.rb +18 -0
- data/lib/threatinator/filters/comments.rb +16 -0
- data/lib/threatinator/filters/whitespace.rb +19 -0
- data/lib/threatinator/output.rb +50 -0
- data/lib/threatinator/parser.rb +23 -0
- data/lib/threatinator/parsers/csv.rb +7 -0
- data/lib/threatinator/parsers/csv/parser.rb +77 -0
- data/lib/threatinator/parsers/getline.rb +8 -0
- data/lib/threatinator/parsers/getline/parser.rb +45 -0
- data/lib/threatinator/parsers/json.rb +8 -0
- data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
- data/lib/threatinator/parsers/json/parser.rb +45 -0
- data/lib/threatinator/parsers/json/record.rb +20 -0
- data/lib/threatinator/parsers/xml.rb +8 -0
- data/lib/threatinator/parsers/xml/node.rb +79 -0
- data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
- data/lib/threatinator/parsers/xml/parser.rb +44 -0
- data/lib/threatinator/parsers/xml/path.rb +70 -0
- data/lib/threatinator/parsers/xml/pattern.rb +53 -0
- data/lib/threatinator/parsers/xml/record.rb +14 -0
- data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
- data/lib/threatinator/plugin_loader.rb +115 -0
- data/lib/threatinator/plugins/output/csv.rb +47 -0
- data/lib/threatinator/plugins/output/null.rb +17 -0
- data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
- data/lib/threatinator/property_definer.rb +101 -0
- data/lib/threatinator/record.rb +22 -0
- data/lib/threatinator/registry.rb +53 -0
- data/lib/threatinator/util.rb +15 -0
- data/spec/feeds/ET_compromised-ip_reputation_spec.rb +50 -0
- data/spec/feeds/alienvault-ip_reputation_spec.rb +50 -0
- data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +50 -0
- data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/autoshun_shunlist_spec.rb +42 -0
- data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +50 -0
- data/spec/feeds/ciarmy-ip_reputation_spec.rb +50 -0
- data/spec/feeds/cruzit-ip_reputation_spec.rb +50 -0
- data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +50 -0
- data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
- data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
- data/spec/feeds/data/arbor_domainlist.txt +11 -0
- data/spec/feeds/data/arbor_ssh.txt +16 -0
- data/spec/feeds/data/autoshun_shunlist.csv +20 -0
- data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
- data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
- data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
- data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
- data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
- data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
- data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
- data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
- data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
- data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
- data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
- data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
- data/spec/feeds/data/dshield_topattackers.xml +4 -0
- data/spec/feeds/data/feodo_domainlist.txt +18 -0
- data/spec/feeds/data/feodo_iplist.txt +20 -0
- data/spec/feeds/data/infiltrated_iplist.txt +16 -0
- data/spec/feeds/data/malc0de_domainlist.txt +18 -0
- data/spec/feeds/data/malc0de_iplist.txt +14 -0
- data/spec/feeds/data/mirc_domainlist.txt +31 -0
- data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
- data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
- data/spec/feeds/data/openbl_iplist.txt +12 -0
- data/spec/feeds/data/palevo_domainlist.txt +25 -0
- data/spec/feeds/data/palevo_iplist.txt +24 -0
- data/spec/feeds/data/phishtank-sample.json.gz +0 -0
- data/spec/feeds/data/spyeye_domainlist.txt +16 -0
- data/spec/feeds/data/spyeye_iplist.txt +19 -0
- data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
- data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
- data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
- data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
- data/spec/feeds/data/zeus_domainlist.txt +27 -0
- data/spec/feeds/dshield_attackers-top1000_spec.rb +43 -0
- data/spec/feeds/feodo-domain_reputation_spec.rb +50 -0
- data/spec/feeds/feodo-ip_reputation_spec.rb +50 -0
- data/spec/feeds/infiltrated-ip_reputation_spec.rb +50 -0
- data/spec/feeds/malc0de-domain_reputation_spec.rb +50 -0
- data/spec/feeds/malc0de-ip_reputation_spec.rb +50 -0
- data/spec/feeds/mirc-domain_reputation_spec.rb +50 -0
- data/spec/feeds/nothink_irc-ip_reputation_spec.rb +50 -0
- data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/openbl-ip_reputation_spec.rb +50 -0
- data/spec/feeds/palevo-domain_reputation_spec.rb +50 -0
- data/spec/feeds/palevo-ip_reputation_spec.rb +50 -0
- data/spec/feeds/phishtank_spec.rb +45 -0
- data/spec/feeds/spyeye-domain_reputation_spec.rb +50 -0
- data/spec/feeds/spyeye-ip_reputation_spec.rb +50 -0
- data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +50 -0
- data/spec/feeds/zeus-domain_reputation_spec.rb +50 -0
- data/spec/feeds/zeus-ip_reputation_spec.rb +50 -0
- data/spec/fixtures/feed/provider1/feed1.feed +6 -0
- data/spec/fixtures/parsers/test.xml +13 -0
- data/spec/fixtures/parsers/test_self_closing.xml +20 -0
- data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
- data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
- data/spec/fixtures/plugins/fake.rb +19 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
- data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
- data/spec/support/bad_feeds/missing_name.feed +6 -0
- data/spec/support/bad_feeds/missing_parser.feed +3 -0
- data/spec/support/bad_feeds/missing_provider.feed +5 -0
- data/spec/support/factories/event.rb +27 -0
- data/spec/support/factories/feed.rb +32 -0
- data/spec/support/factories/feed_builder.rb +65 -0
- data/spec/support/factories/feed_registry.rb +8 -0
- data/spec/support/factories/output.rb +11 -0
- data/spec/support/factories/record.rb +17 -0
- data/spec/support/factories/xml_node.rb +33 -0
- data/spec/support/helpers/io.rb +11 -0
- data/spec/support/helpers/models.rb +13 -0
- data/spec/support/shared/action_builder.rb +47 -0
- data/spec/support/shared/decoder.rb +70 -0
- data/spec/support/shared/feeds.rb +218 -0
- data/spec/support/shared/fetcher.rb +48 -0
- data/spec/support/shared/filter.rb +14 -0
- data/spec/support/shared/io-like.rb +7 -0
- data/spec/support/shared/output.rb +120 -0
- data/spec/support/shared/parsers.rb +51 -0
- data/spec/support/shared/record.rb +111 -0
- data/spec/threatinator/actions/list/action_spec.rb +93 -0
- data/spec/threatinator/actions/run/action_spec.rb +89 -0
- data/spec/threatinator/actions/run/config_spec.rb +39 -0
- data/spec/threatinator/actions/run/coverage_observer_spec.rb +116 -0
- data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
- data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
- data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
- data/spec/threatinator/cli_spec.rb +175 -0
- data/spec/threatinator/config/base_spec.rb +39 -0
- data/spec/threatinator/config/feed_search_spec.rb +76 -0
- data/spec/threatinator/decoders/gzip_spec.rb +75 -0
- data/spec/threatinator/event_builder_spec.rb +33 -0
- data/spec/threatinator/event_spec.rb +30 -0
- data/spec/threatinator/feed_builder_spec.rb +636 -0
- data/spec/threatinator/feed_registry_spec.rb +198 -0
- data/spec/threatinator/feed_runner_spec.rb +155 -0
- data/spec/threatinator/feed_spec.rb +169 -0
- data/spec/threatinator/fetcher_spec.rb +12 -0
- data/spec/threatinator/fetchers/http_spec.rb +32 -0
- data/spec/threatinator/filter_spec.rb +13 -0
- data/spec/threatinator/filters/block_spec.rb +16 -0
- data/spec/threatinator/filters/comments_spec.rb +13 -0
- data/spec/threatinator/filters/whitespace_spec.rb +12 -0
- data/spec/threatinator/parser_spec.rb +13 -0
- data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
- data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
- data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
- data/spec/threatinator/parsers/json/record_spec.rb +30 -0
- data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
- data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
- data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
- data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
- data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
- data/spec/threatinator/plugin_loader_spec.rb +238 -0
- data/spec/threatinator/plugins/output/csv_spec.rb +46 -0
- data/spec/threatinator/plugins/output/null_spec.rb +17 -0
- data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
- data/spec/threatinator/property_definer_spec.rb +155 -0
- data/spec/threatinator/record_spec.rb +19 -0
- data/spec/threatinator/registry_spec.rb +97 -0
- data/spec/threatinator/runner_spec.rb +273 -0
- metadata +376 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'threatinator/cli/action_builder'
|
|
2
|
+
require 'threatinator/actions/run'
|
|
3
|
+
require 'threatinator/actions/run/coverage_observer'
|
|
4
|
+
require 'csv'
|
|
5
|
+
|
|
6
|
+
module Threatinator
|
|
7
|
+
module CLI
|
|
8
|
+
class RunActionBuilder < ActionBuilder
|
|
9
|
+
def initialize(opts, args, config_class)
|
|
10
|
+
super(opts, args)
|
|
11
|
+
@config_class = config_class
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build
|
|
15
|
+
Threatinator::Actions::Run::Action.new(feed_registry, config)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config
|
|
19
|
+
run_hash = config_hash["run"] || {}
|
|
20
|
+
run_hash['observers'] ||= []
|
|
21
|
+
|
|
22
|
+
if filename = run_hash['coverage_output']
|
|
23
|
+
observer = Threatinator::Actions::Run::CoverageObserver.new(filename)
|
|
24
|
+
run_hash['observers'] << observer
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
config = @config_class.new(run_hash)
|
|
28
|
+
|
|
29
|
+
if config.feed_provider.nil? && provider = extra_args.shift
|
|
30
|
+
config.feed_provider = provider
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if config.feed_name.nil? && name = extra_args.shift
|
|
34
|
+
config.feed_name = name
|
|
35
|
+
end
|
|
36
|
+
config
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'virtus'
|
|
2
|
+
module Threatinator
|
|
3
|
+
module Config
|
|
4
|
+
class Base
|
|
5
|
+
include Virtus.model
|
|
6
|
+
|
|
7
|
+
def self.properties(namespace = nil)
|
|
8
|
+
ret = {}
|
|
9
|
+
self.attribute_set.each do |attribute|
|
|
10
|
+
name = attribute.name.to_s
|
|
11
|
+
unless namespace.nil?
|
|
12
|
+
name = [namespace, name].join('.')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if attribute.primitive.ancestors.include?(Threatinator::Config::Base)
|
|
16
|
+
ret.merge!(attribute.primitive.properties(name))
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc = attribute.options[:description]
|
|
21
|
+
case desc
|
|
22
|
+
when nil
|
|
23
|
+
next
|
|
24
|
+
when ::Proc
|
|
25
|
+
desc = desc.call(self, attribute)
|
|
26
|
+
end
|
|
27
|
+
ret[name] = [desc, attribute.type]
|
|
28
|
+
end
|
|
29
|
+
ret
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'threatinator/config/base'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
module Config
|
|
5
|
+
class FeedSearch < Threatinator::Config::Base
|
|
6
|
+
DEFAULT_FEED_PATH = File.expand_path("../../../../feeds", __FILE__)
|
|
7
|
+
|
|
8
|
+
attribute :exclude_default, Boolean, default: false,
|
|
9
|
+
description: 'Exclude default path from feed search path'
|
|
10
|
+
|
|
11
|
+
attribute :path, Array[String],
|
|
12
|
+
description: 'The paths to search for feeds'
|
|
13
|
+
|
|
14
|
+
# @return [Array<String>] An array of paths to search
|
|
15
|
+
def search_path
|
|
16
|
+
ret = self.path
|
|
17
|
+
if self.exclude_default == false
|
|
18
|
+
ret = ret + [DEFAULT_FEED_PATH]
|
|
19
|
+
end
|
|
20
|
+
ret
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Threatinator
|
|
2
|
+
# Decodes/Extracts data from an input IO, producing a new IO. The decoder is
|
|
3
|
+
# initialized with a configuration, and then #decode is called upon an IO
|
|
4
|
+
# object.
|
|
5
|
+
class Decoder
|
|
6
|
+
attr_reader :encoding
|
|
7
|
+
|
|
8
|
+
# @param [Hash] opts An options hash
|
|
9
|
+
# @option opts [String] :encoding The encoding for the output IO. Defaults
|
|
10
|
+
# to "utf-8"
|
|
11
|
+
def initialize(opts = {})
|
|
12
|
+
@encoding = opts[:encoding] || "utf-8"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Decodes an input IO, returning a brand new IO.
|
|
16
|
+
# @param [IO] io The IO to decode
|
|
17
|
+
# @return [IO] A new IO.
|
|
18
|
+
def decode(io)
|
|
19
|
+
#:nocov:
|
|
20
|
+
raise NotImplementedError.new("#{self.class}#decode not implemented!")
|
|
21
|
+
#:nocov:
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'threatinator/decoder'
|
|
2
|
+
require 'threatinator/exceptions'
|
|
3
|
+
require 'zlib'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
require 'pp'
|
|
6
|
+
|
|
7
|
+
module Threatinator
|
|
8
|
+
module Decoders
|
|
9
|
+
class Gzip < Threatinator::Decoder
|
|
10
|
+
# Decompresses the io using Gzip.
|
|
11
|
+
# @param (see Threatinator::Decoder#decode)
|
|
12
|
+
def decode(io)
|
|
13
|
+
zio = Zlib::GzipReader.new(io, encoding: "binary")
|
|
14
|
+
tempfile = Tempfile.new("threatinator", encoding: "binary")
|
|
15
|
+
while chunk = zio.read(10240)
|
|
16
|
+
tempfile.write(chunk)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
zio.close
|
|
20
|
+
io.close unless io.closed?
|
|
21
|
+
tempfile.rewind
|
|
22
|
+
tempfile.set_encoding(self.encoding)
|
|
23
|
+
tempfile
|
|
24
|
+
rescue Zlib::GzipFile::Error => e
|
|
25
|
+
raise Threatinator::Exceptions::DecoderError.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'threatinator/property_definer'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
class Event
|
|
5
|
+
include Threatinator::PropertyDefiner
|
|
6
|
+
|
|
7
|
+
VALID_TYPES = Set.new([:c2, :attacker, :malware_host, :spamming, :scanning, :phishing])
|
|
8
|
+
|
|
9
|
+
def initialize(opts = {})
|
|
10
|
+
_parse_properties(opts)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
property :feed_provider, type: String
|
|
14
|
+
property :feed_name, type: String
|
|
15
|
+
property :type, type: Symbol, validate: lambda { |obj, val| VALID_TYPES.include?(val) }
|
|
16
|
+
property :ipv4s, type: Array, default: lambda { Array.new }
|
|
17
|
+
property :fqdns, type: Array, default: lambda { Array.new }
|
|
18
|
+
|
|
19
|
+
def add_ipv4(ipv4)
|
|
20
|
+
self.ipv4s << ipv4
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_fqdn(fqdn)
|
|
24
|
+
self.fqdns << fqdn
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'threatinator/event'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
class EventBuilder
|
|
5
|
+
attr_reader :total
|
|
6
|
+
def initialize(feed)
|
|
7
|
+
@feed = feed
|
|
8
|
+
@built_events = []
|
|
9
|
+
@total = 0
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each_built_event
|
|
13
|
+
@built_events.each do |event|
|
|
14
|
+
yield event
|
|
15
|
+
end
|
|
16
|
+
@built_events.clear
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def count
|
|
20
|
+
@built_events.count
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def clear
|
|
24
|
+
@built_events.clear
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_event_proc
|
|
28
|
+
self.method(:create_event).to_proc
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_event
|
|
32
|
+
event = Threatinator::Event.new
|
|
33
|
+
event.feed_provider = @feed.provider
|
|
34
|
+
event.feed_name = @feed.name
|
|
35
|
+
yield(event)
|
|
36
|
+
@total += 1
|
|
37
|
+
@built_events << event
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Threatinator
|
|
2
|
+
module Exceptions
|
|
3
|
+
# Indicates that a fetch failed.
|
|
4
|
+
class FetchFailed < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Indicates that the decode operation failed
|
|
8
|
+
class DecoderError < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class ParseError < StandardError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class PluginLoadError < StandardError
|
|
15
|
+
attr_reader :cause
|
|
16
|
+
def initialize(message, cause = nil)
|
|
17
|
+
@cause = cause
|
|
18
|
+
unless cause.nil?
|
|
19
|
+
message = "#{message} : #{cause.class} : #{cause}"
|
|
20
|
+
self.set_backtrace cause.backtrace
|
|
21
|
+
end
|
|
22
|
+
super(message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class UnknownPlugin < StandardError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class CouldNotFindOutputConfigError < StandardError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class InvalidAttributeError < StandardError
|
|
33
|
+
attr_reader :attribute, :got
|
|
34
|
+
def initialize(attribute, got)
|
|
35
|
+
@attribute = attribute
|
|
36
|
+
@got = got
|
|
37
|
+
super("Invalid value for attribute '#{attribute}'. Got " + got.inspect)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class AlreadyRegisteredError < StandardError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class UnknownFeed < StandardError
|
|
45
|
+
attr_reader :provider, :name
|
|
46
|
+
def initialize(provider, name)
|
|
47
|
+
@provider = provider
|
|
48
|
+
@name = name
|
|
49
|
+
super("Failed to find feed with provider '#{provider}' and name '#{name}'")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class FeedFileNotFoundError < StandardError
|
|
54
|
+
def initialize(filename)
|
|
55
|
+
@filename = filename
|
|
56
|
+
super("Failed to open/read feed file '#{filename}'")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'threatinator/exceptions'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
class Feed
|
|
5
|
+
# @param [Hash] opts Options hash
|
|
6
|
+
# @option opts [String] :provider The name of the provider
|
|
7
|
+
# @option opts [String] :name The name of the feed
|
|
8
|
+
# @option opts [Proc] :parser_block A block that will be called by the
|
|
9
|
+
# parser each time it processes a record.
|
|
10
|
+
# @option opts [Proc] :parser_builder A proc that, when called, will
|
|
11
|
+
# return a brand new instance of a Threatinator::Parser.
|
|
12
|
+
# @option opts [Proc] :fetcher_builder A proc that, when called, will
|
|
13
|
+
# return a brand new instance of a Threatinator::Fetcher.
|
|
14
|
+
# @option opts [Array<Proc>] :filter_builders An array of procs that,
|
|
15
|
+
# when called, will each return an instance of a filter (something that
|
|
16
|
+
# responds to :filter?)
|
|
17
|
+
# @option opts [Array<Proc>] :decoder_builders An array of procs that,
|
|
18
|
+
# when called, will each return an instance of a Threatinator::Decoder
|
|
19
|
+
def initialize(opts = {})
|
|
20
|
+
@provider = opts.delete(:provider)
|
|
21
|
+
@name = opts.delete(:name)
|
|
22
|
+
@parser_block = opts.delete(:parser_block)
|
|
23
|
+
|
|
24
|
+
@parser_builder = opts.delete(:parser_builder)
|
|
25
|
+
@fetcher_builder = opts.delete(:fetcher_builder)
|
|
26
|
+
@filter_builders = opts.delete(:filter_builders) || []
|
|
27
|
+
@decoder_builders = opts.delete(:decoder_builders) || []
|
|
28
|
+
validate!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def provider
|
|
32
|
+
@provider.dup
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def name
|
|
36
|
+
@name.dup
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parser_block
|
|
40
|
+
@parser_block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fetcher_builder
|
|
44
|
+
@fetcher_builder
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parser_builder
|
|
48
|
+
@parser_builder
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def filter_builders
|
|
52
|
+
@filter_builders.dup
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def decoder_builders
|
|
56
|
+
@decoder_builders.dup
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate!
|
|
60
|
+
validate_attribute!(:provider, @provider) { |x| x.kind_of?(::String) }
|
|
61
|
+
validate_attribute!(:name, @name) { |x| x.kind_of?(::String) }
|
|
62
|
+
validate_attribute!(:parser_block, @parser_block) { |x| x.kind_of?(::Proc) }
|
|
63
|
+
validate_attribute!(:fetcher_builder, @fetcher_builder) { |x| x.kind_of?(::Proc) }
|
|
64
|
+
validate_attribute!(:parser_builder, @parser_builder) { |x| x.kind_of?(::Proc) }
|
|
65
|
+
validate_attribute!(:filter_builders, @filter_builders) do |x|
|
|
66
|
+
x.kind_of?(::Array) &&
|
|
67
|
+
x.all? { |e| e.kind_of?(::Proc) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
validate_attribute!(:decoder_builders, @decoder_builders) do |x|
|
|
71
|
+
x.kind_of?(::Array) &&
|
|
72
|
+
x.all? { |e| e.kind_of?(::Proc) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_attribute!(name, val, &block)
|
|
77
|
+
unless block.call(val) == true
|
|
78
|
+
raise Threatinator::Exceptions::InvalidAttributeError.new(name, val)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require 'docile'
|
|
2
|
+
require 'threatinator/feed'
|
|
3
|
+
require 'threatinator/exceptions'
|
|
4
|
+
require 'threatinator/decoders/gzip'
|
|
5
|
+
require 'threatinator/fetchers/http'
|
|
6
|
+
require 'threatinator/parsers/getline'
|
|
7
|
+
require 'threatinator/parsers/csv'
|
|
8
|
+
require 'threatinator/parsers/json'
|
|
9
|
+
require 'threatinator/parsers/xml'
|
|
10
|
+
require 'threatinator/filters/block'
|
|
11
|
+
require 'threatinator/filters/whitespace'
|
|
12
|
+
require 'threatinator/filters/comments'
|
|
13
|
+
|
|
14
|
+
module Threatinator
|
|
15
|
+
class FeedBuilder
|
|
16
|
+
def provider(provider_name)
|
|
17
|
+
@provider = provider_name
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def name(name)
|
|
22
|
+
@name = name
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def fetch_http(url, opts = {})
|
|
27
|
+
opts[:url] = url
|
|
28
|
+
@fetcher_builder = lambda do
|
|
29
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
30
|
+
Threatinator::Fetchers::Http.new(opts_dup)
|
|
31
|
+
end
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_xml(pattern_string, opts = {}, &block)
|
|
36
|
+
@parser_builder = lambda do
|
|
37
|
+
pattern = Threatinator::Parsers::XML::Pattern.new(pattern_string)
|
|
38
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
39
|
+
opts_dup[:pattern] = pattern
|
|
40
|
+
Threatinator::Parsers::XML::Parser.new(opts_dup, &block)
|
|
41
|
+
end
|
|
42
|
+
@parser_block = block
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parse_json(opts = {}, &block)
|
|
47
|
+
@parser_builder = lambda do
|
|
48
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
49
|
+
Threatinator::Parsers::JSON::Parser.new(opts_dup, &block)
|
|
50
|
+
end
|
|
51
|
+
@parser_block = block
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse_eachline(opts = {}, &block)
|
|
56
|
+
@parser_builder = lambda do
|
|
57
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
58
|
+
Threatinator::Parsers::Getline::Parser.new(opts_dup, &block)
|
|
59
|
+
end
|
|
60
|
+
@parser_block = block
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def parse_csv(opts = {}, &block)
|
|
65
|
+
@parser_builder = lambda do
|
|
66
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
67
|
+
Threatinator::Parsers::CSV::Parser.new(opts_dup, &block)
|
|
68
|
+
end
|
|
69
|
+
@parser_block = block
|
|
70
|
+
self
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Specify a block filter for the parser
|
|
74
|
+
def filter(&block)
|
|
75
|
+
@filter_builders ||= []
|
|
76
|
+
@filter_builders << lambda { Threatinator::Filters::Block.new(block) }
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Filter out whitespace lines. Only works on line-based text.
|
|
81
|
+
def filter_whitespace
|
|
82
|
+
@filter_builders ||= []
|
|
83
|
+
@filter_builders << lambda { Threatinator::Filters::Whitespace.new }
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Filter out whitespace lines. Only works on line-based text.
|
|
88
|
+
def filter_comments
|
|
89
|
+
@filter_builders ||= []
|
|
90
|
+
@filter_builders << lambda { Threatinator::Filters::Comments.new }
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Add the Gzip decoder
|
|
95
|
+
def decode_gzip
|
|
96
|
+
decoder_builders << lambda { Threatinator::Decoders::Gzip.new }
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
alias_method :extract_gzip, :decode_gzip
|
|
100
|
+
alias_method :gunzip, :decode_gzip
|
|
101
|
+
|
|
102
|
+
def decoder_builders
|
|
103
|
+
@decoder_builders ||= []
|
|
104
|
+
end
|
|
105
|
+
private :decoder_builders
|
|
106
|
+
|
|
107
|
+
def build
|
|
108
|
+
Feed.new(
|
|
109
|
+
:provider => @provider,
|
|
110
|
+
:name => @name,
|
|
111
|
+
:parser_block => @parser_block,
|
|
112
|
+
:fetcher_builder => @fetcher_builder,
|
|
113
|
+
:parser_builder => @parser_builder,
|
|
114
|
+
:filter_builders => @filter_builders,
|
|
115
|
+
:decoder_builders => decoder_builders
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Loads the provided file, and generates a builder from it.
|
|
120
|
+
# @param [String] filename The name of the file to read the feed from
|
|
121
|
+
# @raise [FeedFileNotFoundError] if the file is not found
|
|
122
|
+
def self.from_file(filename)
|
|
123
|
+
begin
|
|
124
|
+
filedata = File.read(filename)
|
|
125
|
+
rescue Errno::ENOENT
|
|
126
|
+
raise Threatinator::Exceptions::FeedFileNotFoundError.new(filename)
|
|
127
|
+
end
|
|
128
|
+
from_string(filedata, filename, 0)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Generates a builder from a string via eval.
|
|
132
|
+
# @param [String] str The DSL code that specifies the feed.
|
|
133
|
+
# @param [String] filename (nil) Passed to eval.
|
|
134
|
+
# @param [String] lineno (nil) Passed to eval.
|
|
135
|
+
# @raise [FeedFileNotFoundError] if the file is not found
|
|
136
|
+
# @see Kernel#eval for details on filename and lineno
|
|
137
|
+
def self.from_string(str, filename = nil, lineno = nil)
|
|
138
|
+
from_dsl do
|
|
139
|
+
args = [str, binding]
|
|
140
|
+
unless filename.nil?
|
|
141
|
+
args << filename
|
|
142
|
+
unless lineno.nil?
|
|
143
|
+
args << lineno
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
eval(*args)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Executes the block parameter within DSL scope
|
|
151
|
+
def self.from_dsl(&block)
|
|
152
|
+
Docile.dsl_eval(self.new, &block)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|