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,47 @@
|
|
|
1
|
+
require 'threatinator/registry'
|
|
2
|
+
require 'threatinator/feed_builder'
|
|
3
|
+
|
|
4
|
+
module Threatinator
|
|
5
|
+
class FeedRegistry < Registry
|
|
6
|
+
# @param [Threatinator::Feed] feed The feed to register
|
|
7
|
+
# @raise [Threatinator::Exceptions::AlreadyRegisteredError] if a feed
|
|
8
|
+
# with the same name and provider is already registered.
|
|
9
|
+
def register(feed)
|
|
10
|
+
super([feed.provider, feed.name], feed)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param [String] provider
|
|
14
|
+
# @param [String] name
|
|
15
|
+
# @return [Threatinator::Feed]
|
|
16
|
+
def get(provider, name)
|
|
17
|
+
super([provider, name])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def each
|
|
21
|
+
return enum_for(:each) unless block_given?
|
|
22
|
+
super do |key, feed|
|
|
23
|
+
yield(feed)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def register_from_file(filename)
|
|
28
|
+
builder = Threatinator::FeedBuilder.from_file(filename)
|
|
29
|
+
feed = builder.build
|
|
30
|
+
register(feed)
|
|
31
|
+
feed
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Builds a new FeedRegistry based on the provided config
|
|
35
|
+
# @param [Threatinator::Config::FeedSearch] config The configuration
|
|
36
|
+
def self.build(config)
|
|
37
|
+
ret = self.new
|
|
38
|
+
config.search_path.each do |path|
|
|
39
|
+
pattern = File.join(path, "**", "*.feed")
|
|
40
|
+
Dir.glob(pattern).each do |filename|
|
|
41
|
+
ret.register_from_file(filename)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
ret
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'threatinator/event_builder'
|
|
2
|
+
require 'observer'
|
|
3
|
+
|
|
4
|
+
module Threatinator
|
|
5
|
+
# Runs those feeds!
|
|
6
|
+
#
|
|
7
|
+
# Has the following observations:
|
|
8
|
+
# :start - start of feed parsing
|
|
9
|
+
#
|
|
10
|
+
# :start_fetch - start of fetching
|
|
11
|
+
# :end_fetch - end of fetching
|
|
12
|
+
#
|
|
13
|
+
# :start_decode - start of decoding
|
|
14
|
+
# :end_decode - end of decoding
|
|
15
|
+
#
|
|
16
|
+
# :start_parse_record - start of record parse
|
|
17
|
+
# - record - The record
|
|
18
|
+
#
|
|
19
|
+
# :record_filtered - Indicates that the record was filtered.
|
|
20
|
+
# - record - The record
|
|
21
|
+
#
|
|
22
|
+
# :record_missed - Indicates that the record was not parsed
|
|
23
|
+
# - record - The record
|
|
24
|
+
#
|
|
25
|
+
# :record_parsed - Indicates that the record WAS parsed
|
|
26
|
+
# - record - The record
|
|
27
|
+
# - events - The events that were parsed out of the
|
|
28
|
+
# record
|
|
29
|
+
#
|
|
30
|
+
# :end_parse_record - when a record has been parsed
|
|
31
|
+
# - record - The record
|
|
32
|
+
#
|
|
33
|
+
# :end - completion of feed parsing
|
|
34
|
+
#
|
|
35
|
+
class FeedRunner
|
|
36
|
+
include Observable
|
|
37
|
+
|
|
38
|
+
# @param [Threatinator::Feed] feed The feed that we want to run.
|
|
39
|
+
# @param [Threatinator::Output] output_formatter
|
|
40
|
+
def initialize(feed, output_formatter, opts = {})
|
|
41
|
+
@feed = feed
|
|
42
|
+
@output_formatter = output_formatter
|
|
43
|
+
@event_builder = Threatinator::EventBuilder.new(@feed)
|
|
44
|
+
@feed_filters = @feed.filter_builders.map { |x| x.call }
|
|
45
|
+
@decoders = @feed.decoder_builders.map { |x| x.call }
|
|
46
|
+
@parser_block = @feed.parser_block
|
|
47
|
+
@create_event_proc = @event_builder.create_event_proc()
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param [Hash] opts The options hash
|
|
51
|
+
# @option opts [IO-like] :io Override the fetcher by providing
|
|
52
|
+
# an IO directly.
|
|
53
|
+
# @option opts [Boolean] :skip_decoding (false) Skip all decoding if set
|
|
54
|
+
# to true. Useful for testing.
|
|
55
|
+
def run(opts = {})
|
|
56
|
+
changed(true); notify_observers(:start)
|
|
57
|
+
skip_decoding = !!opts.delete(:skip_decoding)
|
|
58
|
+
|
|
59
|
+
unless io = opts.delete(:io)
|
|
60
|
+
fetcher = @feed.fetcher_builder.call()
|
|
61
|
+
changed(true); notify_observers(:start_fetch)
|
|
62
|
+
io = fetcher.fetch()
|
|
63
|
+
changed(true); notify_observers(:end_fetch)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
unless skip_decoding == true
|
|
67
|
+
changed(true); notify_observers(:start_decode)
|
|
68
|
+
@decoders.each do |decoder|
|
|
69
|
+
io = decoder.decode(io)
|
|
70
|
+
end
|
|
71
|
+
changed(true); notify_observers(:end_decode)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
parser = @feed.parser_builder.call()
|
|
75
|
+
|
|
76
|
+
parser.run(io) do |record|
|
|
77
|
+
rr = parse_record(record)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
changed(true); notify_observers(:end)
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parse_record(record)
|
|
85
|
+
@event_builder.clear
|
|
86
|
+
events = []
|
|
87
|
+
changed(true); notify_observers(:start_parse_record, record)
|
|
88
|
+
|
|
89
|
+
if @feed_filters.any? { |filter| filter.filter?(record) }
|
|
90
|
+
changed(true); notify_observers(:record_filtered, record)
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
@parser_block.call(@create_event_proc, record)
|
|
94
|
+
if @event_builder.count == 0
|
|
95
|
+
changed(true); notify_observers(:record_missed, record)
|
|
96
|
+
# Keep track of the fact that this line did not generate any events?
|
|
97
|
+
else
|
|
98
|
+
@event_builder.each_built_event do |event|
|
|
99
|
+
events << event
|
|
100
|
+
@output_formatter.handle_event(event)
|
|
101
|
+
end
|
|
102
|
+
changed(true); notify_observers(:record_parsed, record, events)
|
|
103
|
+
end
|
|
104
|
+
return
|
|
105
|
+
ensure
|
|
106
|
+
changed(true); notify_observers(:end_parse_record, record)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Runs a feed
|
|
110
|
+
# @param [Threatinator::Feed] feed The feed to run
|
|
111
|
+
# @param [Threatinator::Output] output The output instance
|
|
112
|
+
# @param [Hash] run_opts Options passed to #run. See #run .
|
|
113
|
+
def self.run(feed, output, run_opts = {})
|
|
114
|
+
self.new(feed, output).run(run_opts)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Threatinator
|
|
2
|
+
class Fetcher
|
|
3
|
+
|
|
4
|
+
# @param [Hash] opts An options hash. See subclasses for details.
|
|
5
|
+
def initialize(opts = {})
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# @return [IO] an IO object
|
|
9
|
+
def fetch
|
|
10
|
+
raise NotImplementedError.new("#{self.class}#fetch not implemented!")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ==(other)
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def eql?(other)
|
|
18
|
+
self.class == other.class &&
|
|
19
|
+
self == other
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'threatinator/fetcher'
|
|
2
|
+
require 'threatinator/exceptions'
|
|
3
|
+
require 'typhoeus'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
module Threatinator
|
|
7
|
+
module Fetchers
|
|
8
|
+
class Http < Threatinator::Fetcher
|
|
9
|
+
attr_reader :url
|
|
10
|
+
# @param [Hash] opts An options hash.
|
|
11
|
+
# @option opts [Addressable::URI] :url The URL that is to be fetched
|
|
12
|
+
# (required)
|
|
13
|
+
#
|
|
14
|
+
def initialize(opts = {})
|
|
15
|
+
@url = opts.delete(:url) or raise ArgumentError.new("Missing :url")
|
|
16
|
+
super(opts)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ==(other)
|
|
20
|
+
@url == other.url && super(other)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [IO] an IO-style object.
|
|
24
|
+
# @raise [Threatinator::Exceptions::FetchFailed] if the fetch fails
|
|
25
|
+
def fetch
|
|
26
|
+
tempfile = Tempfile.new("threatinator_http")
|
|
27
|
+
request = Typhoeus::Request.new(@url, ssl_verifypeer: false)
|
|
28
|
+
request.on_headers do |response|
|
|
29
|
+
if response.response_code != 200
|
|
30
|
+
|
|
31
|
+
raise Threatinator::Exceptions::FetchFailed.new("Request failed!")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
request.on_body do |chunk|
|
|
35
|
+
tempfile.write(chunk)
|
|
36
|
+
end
|
|
37
|
+
# Run it
|
|
38
|
+
request.run
|
|
39
|
+
# Reset the IO to the beginning of the file
|
|
40
|
+
tempfile.rewind
|
|
41
|
+
tempfile
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Threatinator
|
|
2
|
+
# Acts as a filter for parser data.
|
|
3
|
+
class Filter
|
|
4
|
+
# What is passed in as arguments depends upon the parser.
|
|
5
|
+
#
|
|
6
|
+
# @param [Threatinator::Record] record The record to filter
|
|
7
|
+
# @return [Boolean] true if filtered, false otherwise.
|
|
8
|
+
def filter?(record)
|
|
9
|
+
raise NotImplementedError.new("#{self.class}.filter?(record) not implemented")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'threatinator/filter'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
module Filters
|
|
5
|
+
# Basic filter that allows for arbitrary filtering.
|
|
6
|
+
class Block < Threatinator::Filter
|
|
7
|
+
def initialize(block)
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @param [Threatinator::Record] record The record to filter
|
|
12
|
+
# @return [Boolean] true if filtered, false otherwise.
|
|
13
|
+
def filter?(record)
|
|
14
|
+
!! @block.call(record)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'threatinator/filter'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
module Filters
|
|
5
|
+
# Filters out any lines of text that begin with a comment '#'
|
|
6
|
+
class Comments < Threatinator::Filter
|
|
7
|
+
# @param [Threatinator::Record] record The record to filter
|
|
8
|
+
# @return [Boolean] true if filtered, false otherwise.
|
|
9
|
+
def filter?(record)
|
|
10
|
+
record.data[0] == '#'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'threatinator/filter'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
module Filters
|
|
5
|
+
# Filters on any lines of text that consist entirely of whitespace
|
|
6
|
+
class Whitespace < Threatinator::Filter
|
|
7
|
+
def initialize()
|
|
8
|
+
@re = /^\s*$/
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @param [Threatinator::Record] record The record to filter
|
|
12
|
+
# @return [Boolean] true if filtered, false otherwise.
|
|
13
|
+
def filter?(record)
|
|
14
|
+
!! @re.match(record.data)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'threatinator/config/base'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
class Output
|
|
5
|
+
def initialize(config)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def handle_event(event)
|
|
9
|
+
#:nocov:
|
|
10
|
+
raise NotImplementedError.new("#{self.class}#handle_event is not implemented")
|
|
11
|
+
#:nocov:
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def finish
|
|
15
|
+
#:nocov:
|
|
16
|
+
raise NotImplementedError.new("#{self.class}#finish is not implemented")
|
|
17
|
+
#:nocov:
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Config < Threatinator::Config::Base
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class FileBasedOutput < Output
|
|
25
|
+
attr_reader :output_io
|
|
26
|
+
protected :output_io
|
|
27
|
+
|
|
28
|
+
def initialize(config)
|
|
29
|
+
super(config)
|
|
30
|
+
if io = config.io
|
|
31
|
+
@output_io = io
|
|
32
|
+
elsif filename = config.filename
|
|
33
|
+
@output_io = File.open(filename, 'w:UTF-8')
|
|
34
|
+
else
|
|
35
|
+
@output_io = $stdout.dup
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def finish
|
|
40
|
+
@output_io.close
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Config < superclass::Config
|
|
44
|
+
attribute :filename, String,
|
|
45
|
+
description: "Path to the file where output will be written"
|
|
46
|
+
|
|
47
|
+
attribute :io
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Threatinator
|
|
2
|
+
class Parser
|
|
3
|
+
# @param [Hash] opts An options hash. See subclasses for details.
|
|
4
|
+
def initialize(opts = {})
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Runs the parser against the provided io, yielding records.
|
|
8
|
+
# @param [IO] io The IO to be parsed.
|
|
9
|
+
def run(io)
|
|
10
|
+
raise NotImplementedError.new("#{self.class}#run not implemented!")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ==(other)
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def eql?(other)
|
|
18
|
+
self.class == other.class &&
|
|
19
|
+
self == other
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'threatinator/record'
|
|
2
|
+
require 'threatinator/parser'
|
|
3
|
+
require 'csv'
|
|
4
|
+
|
|
5
|
+
module Threatinator
|
|
6
|
+
module Parsers
|
|
7
|
+
module CSV
|
|
8
|
+
# Parses an IO, yielding a record with a CSV::Row.
|
|
9
|
+
class Parser < Threatinator::Parser
|
|
10
|
+
attr_reader :csv_opts, :row_separator, :col_separator, :headers
|
|
11
|
+
|
|
12
|
+
# @param [Hash] opts
|
|
13
|
+
# @option opts [String, :auto] :row_separator A string that represent the row
|
|
14
|
+
# separator. Identical to ::CSV.new's :row_sep.
|
|
15
|
+
# @option opts [String] :col_separator A string that represents the column
|
|
16
|
+
# separator. Identical to ::CSV.new's :col_sep.
|
|
17
|
+
# @option opts [Array<String>, :first_row, true, false] :headers The header
|
|
18
|
+
# configuration. Identical to ::CSV.new's :headers.
|
|
19
|
+
# @option opts [Hash] :csv_opts A hash of options that will be passed to
|
|
20
|
+
# Ruby's CSV.new.
|
|
21
|
+
# @see ::CSV
|
|
22
|
+
def initialize(opts = {})
|
|
23
|
+
@csv_opts = {}.merge(opts.delete(:csv_opts) || {})
|
|
24
|
+
@row_separator = opts.delete(:row_separator)
|
|
25
|
+
@col_separator = opts.delete(:col_separator)
|
|
26
|
+
@headers = opts.delete(:headers)
|
|
27
|
+
|
|
28
|
+
super(opts)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ==(other)
|
|
32
|
+
@csv_opts == other.csv_opts &&
|
|
33
|
+
@row_separator == other.row_separator &&
|
|
34
|
+
@col_separator == other.col_separator &&
|
|
35
|
+
@headers == other.headers &&
|
|
36
|
+
super(other)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def _build_csv_opts
|
|
40
|
+
opts = {}.merge(@csv_opts)
|
|
41
|
+
opts[:return_headers] = true
|
|
42
|
+
opts[:row_sep] = @row_separator unless @row_separator.nil?
|
|
43
|
+
opts[:col_sep] = @col_separator unless @col_separator.nil?
|
|
44
|
+
opts[:headers] = @headers unless @headers.nil?
|
|
45
|
+
opts
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param [IO] io
|
|
49
|
+
# @yield [record] Gives one line to the block
|
|
50
|
+
# @yieldparam record [Record] a record
|
|
51
|
+
def run(io)
|
|
52
|
+
lineno = 1
|
|
53
|
+
previous_pos = io.pos
|
|
54
|
+
csv = ::CSV.new(io, _build_csv_opts())
|
|
55
|
+
csv.each do |row|
|
|
56
|
+
begin
|
|
57
|
+
if row.kind_of?(::CSV::Row)
|
|
58
|
+
next if row.header_row?
|
|
59
|
+
row = row.to_hash
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
yield Record.new(row,
|
|
63
|
+
line_number: lineno,
|
|
64
|
+
pos_start: previous_pos,
|
|
65
|
+
pos_end: io.pos)
|
|
66
|
+
|
|
67
|
+
ensure
|
|
68
|
+
previous_pos = io.pos
|
|
69
|
+
lineno += 1
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|