shadowbq-threatinator 0.5.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +66 -0
- data/CONTRIBUTING.md +119 -0
- data/Gemfile +38 -0
- data/LICENSE +165 -0
- data/README.md +101 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/bin/threatinator +5 -0
- data/bin/threatinator_loader +21 -0
- data/feeds/ET_block-ip_reputation.feed +27 -0
- data/feeds/ET_compromised-ip_reputation.feed +20 -0
- data/feeds/ET_openbadlist-ip_reputation.feed +36 -0
- data/feeds/alienvault-ip_reputation.feed +39 -0
- data/feeds/arbor_fastflux-domain_reputation.feed +19 -0
- data/feeds/arbor_ssh-ip_reputation.feed +24 -0
- data/feeds/autoshun_shunlist.feed +17 -0
- data/feeds/bambenek_c2_masterlist-domain_reputation.feed +16 -0
- data/feeds/bambenek_c2_masterlist-ip_reputation.feed +16 -0
- data/feeds/bambenek_dga_feed-domain_reputation.feed +16 -0
- data/feeds/berkeley-ip_reputation.feed +25 -0
- data/feeds/bitcash_cz_blacklist.feed +22 -0
- data/feeds/blocklist_de_apache-ip_reputation.feed +26 -0
- data/feeds/blocklist_de_bots-ip_reputation.feed +26 -0
- data/feeds/blocklist_de_ftp-ip_reputation.feed +25 -0
- data/feeds/blocklist_de_imap-ip_reputation.feed +25 -0
- data/feeds/blocklist_de_pop3-ip_reputation.feed +26 -0
- data/feeds/blocklist_de_proftpd-ip_reputation.feed +26 -0
- data/feeds/blocklist_de_sip-ip_reputation.feed +25 -0
- data/feeds/blocklist_de_ssh-ip_reputation.feed +25 -0
- data/feeds/blocklist_de_strongips-ip_reputation.feed +25 -0
- data/feeds/botscout-ip_reputation.feed +25 -0
- data/feeds/cert_mxpoison-ip_reputation.feed +22 -0
- data/feeds/chaosreigns-ip_reputation.feed +37 -0
- data/feeds/ciarmy-ip_reputation.feed +20 -0
- data/feeds/cruzit-ip_reputation.feed +30 -0
- data/feeds/cydef_torexit-ip_reputation.feed +25 -0
- data/feeds/dan_me_uk_torlist-ip_reputation.feed +25 -0
- data/feeds/danger_bruteforce-ip_reputation.feed +24 -0
- data/feeds/dshield_attackers-top1000.feed +34 -0
- data/feeds/falconcrest-ip_reputation.feed +19 -0
- data/feeds/feodo-domain_reputation.feed +19 -0
- data/feeds/feodo-ip_reputation.feed +20 -0
- data/feeds/h3x_asprox.feed +18 -0
- data/feeds/hosts-file_hphostspartial-domain_reputation.feed +19 -0
- data/feeds/infiltrated-ip_reputation.feed +26 -0
- data/feeds/infiltrated_vabl-ip_reputation.feed +30 -0
- data/feeds/isc_suspicious_high-domain_reputation.feed +26 -0
- data/feeds/isc_suspicious_low-domain_reputation.feed +26 -0
- data/feeds/isc_suspicious_medium-domain_reputation.feed +26 -0
- data/feeds/malc0de-domain_reputation.feed +24 -0
- data/feeds/malc0de-ip_reputation.feed +26 -0
- data/feeds/malwaredomainlist-url_reputation.feed +18 -0
- data/feeds/malwaredomains-domain_reputation.feed +29 -0
- data/feeds/malwaredomains_dyndns-domain_reputation.feed +29 -0
- data/feeds/malwaredomains_justdomains-domain_reputation.feed +20 -0
- data/feeds/mirc-domain_reputation.feed +30 -0
- data/feeds/multiproxy-ip_reputation.feed +22 -0
- data/feeds/nothink_irc-ip_reputation.feed +23 -0
- data/feeds/nothink_ssh-ip_reputation.feed +21 -0
- data/feeds/openbl-ip_reputation.feed +21 -0
- data/feeds/openphish-url_reputation.feed +24 -0
- data/feeds/packetmail_perimeterbad-ip_reputation.feed +28 -0
- data/feeds/palevo-domain_reputation.feed +22 -0
- data/feeds/palevo-ip_reputation.feed +23 -0
- data/feeds/phishtank.feed +22 -0
- data/feeds/sigmaproject_atma.feed +27 -0
- data/feeds/sigmaproject_spyware.feed +28 -0
- data/feeds/sigmaproject_webexploit.feed +26 -0
- data/feeds/snort_bpf-ip_reputation.feed +19 -0
- data/feeds/spyeye-domain_reputation.feed +18 -0
- data/feeds/spyeye-ip_reputation.feed +19 -0
- data/feeds/steeman-ip_reputation.feed +20 -0
- data/feeds/t-arend-de_ssh-ip_reputation.feed +20 -0
- data/feeds/the_haleys_ssh-ip_reputation.feed +20 -0
- data/feeds/trustedsec-ip_reputation.feed +18 -0
- data/feeds/virbl-ip_reputation.feed +25 -0
- data/feeds/vxvault-url_reputation.feed +23 -0
- data/feeds/yourcmc_ssh-ip_reputation.feed +20 -0
- data/feeds/yoyo_adservers-domain_reputation.feed +17 -0
- data/feeds/zeus-domain_reputation.feed +19 -0
- data/feeds/zeus-ip_reputation.feed +21 -0
- data/lib/threatinator/action.rb +14 -0
- data/lib/threatinator/actions/list/action.rb +97 -0
- data/lib/threatinator/actions/list/config.rb +12 -0
- data/lib/threatinator/actions/list.rb +2 -0
- data/lib/threatinator/actions/run/action.rb +57 -0
- data/lib/threatinator/actions/run/config.rb +32 -0
- data/lib/threatinator/actions/run/coverage_observer.rb +59 -0
- data/lib/threatinator/actions/run/output_config.rb +59 -0
- data/lib/threatinator/actions/run/status_observer.rb +37 -0
- data/lib/threatinator/actions/run.rb +2 -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 +123 -0
- data/lib/threatinator/cli/run_action_builder.rb +41 -0
- data/lib/threatinator/cli.rb +19 -0
- data/lib/threatinator/config/base.rb +35 -0
- data/lib/threatinator/config/feed_search.rb +25 -0
- data/lib/threatinator/config/logger.rb +14 -0
- data/lib/threatinator/config.rb +7 -0
- data/lib/threatinator/decoder.rb +24 -0
- data/lib/threatinator/decoders/gzip.rb +30 -0
- data/lib/threatinator/event.rb +63 -0
- data/lib/threatinator/event_builder.rb +70 -0
- data/lib/threatinator/exceptions.rb +58 -0
- data/lib/threatinator/feed.rb +88 -0
- data/lib/threatinator/feed_builder.rb +161 -0
- data/lib/threatinator/feed_registry.rb +47 -0
- data/lib/threatinator/feed_runner.rb +177 -0
- data/lib/threatinator/fetcher.rb +22 -0
- data/lib/threatinator/fetchers/http.rb +50 -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/logger.rb +66 -0
- data/lib/threatinator/logging.rb +20 -0
- data/lib/threatinator/model/base.rb +23 -0
- data/lib/threatinator/model/collection.rb +89 -0
- data/lib/threatinator/model/observables/fqdn_collection.rb +15 -0
- data/lib/threatinator/model/observables/ipv4.rb +33 -0
- data/lib/threatinator/model/observables/ipv4_collection.rb +14 -0
- data/lib/threatinator/model/observables/url_collection.rb +16 -0
- data/lib/threatinator/model/validations/type.rb +21 -0
- data/lib/threatinator/model/validations.rb +1 -0
- data/lib/threatinator/output.rb +50 -0
- data/lib/threatinator/parser.rb +23 -0
- data/lib/threatinator/parsers/csv/parser.rb +77 -0
- data/lib/threatinator/parsers/csv.rb +7 -0
- data/lib/threatinator/parsers/getline/parser.rb +45 -0
- data/lib/threatinator/parsers/getline.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/json.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/parsers/xml.rb +8 -0
- data/lib/threatinator/plugin_loader.rb +115 -0
- data/lib/threatinator/plugins/output/amqp/config.rb +18 -0
- data/lib/threatinator/plugins/output/amqp.rb +41 -0
- data/lib/threatinator/plugins/output/csv.rb +58 -0
- data/lib/threatinator/plugins/output/json/config.rb +14 -0
- data/lib/threatinator/plugins/output/json.rb +53 -0
- data/lib/threatinator/plugins/output/null.rb +17 -0
- data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
- data/lib/threatinator/record.rb +22 -0
- data/lib/threatinator/registry.rb +53 -0
- data/lib/threatinator/util.rb +15 -0
- data/lib/threatinator.rb +3 -0
- data/spec/feeds/ET_block-ip_reputation_spec.rb +50 -0
- data/spec/feeds/ET_compromised-ip_reputation_spec.rb +47 -0
- data/spec/feeds/ET_openbadlist-ip_reputation_spec.rb +56 -0
- data/spec/feeds/alienvault-ip_reputation_spec.rb +46 -0
- data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +46 -0
- data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +46 -0
- data/spec/feeds/autoshun_shunlist_spec.rb +38 -0
- data/spec/feeds/bambenek_c2_masterlist-domain_reputation_spec.rb +38 -0
- data/spec/feeds/bambenek_c2_masterlist-ip_reputation_spec.rb +39 -0
- data/spec/feeds/bambenek_dga_feed-domain_reputation_spec.rb +39 -0
- data/spec/feeds/berkeley-ip_reputation_spec.rb +47 -0
- data/spec/feeds/bitcash_cz_blacklist-ip_reputation_spec.rb +50 -0
- data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +47 -0
- data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +47 -0
- data/spec/feeds/botscout-ip_reputation_spec.rb +50 -0
- data/spec/feeds/cert_mxpoison-ip_reputation_spec.rb +47 -0
- data/spec/feeds/chaosreigns-ip_reputation_spec.rb +50 -0
- data/spec/feeds/ciarmy-ip_reputation_spec.rb +47 -0
- data/spec/feeds/cruzit-ip_reputation_spec.rb +47 -0
- data/spec/feeds/cydef_torexit-ip_reputation_spec.rb +47 -0
- data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +47 -0
- data/spec/feeds/danger_bruteforce-ip_reputation_spec.rb +47 -0
- data/spec/feeds/data/ET_block-ip_reputation.txt +80 -0
- data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
- data/spec/feeds/data/ET_openbadlist-ip_reputation.txt +62 -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/bambenek_c2-dommasterlist.csv +30 -0
- data/spec/feeds/data/bambenek_c2-ipmasterlist.csv +27 -0
- data/spec/feeds/data/bambenek_dga_feed.csv +42 -0
- data/spec/feeds/data/berkeley.txt +29 -0
- data/spec/feeds/data/bitcash_cz_blacklist.txt +7 -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/botscout-ip-reputation.txt +713 -0
- data/spec/feeds/data/cert_mxpoison-ip_reputation.txt +17 -0
- data/spec/feeds/data/chaosreigns-ip-reputation.txt +26 -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/cydef_torexit-ip_reputation.txt +27 -0
- data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
- data/spec/feeds/data/danger_bruteforce-ip_reputation.txt +12 -0
- data/spec/feeds/data/dshield_topattackers.xml +4 -0
- data/spec/feeds/data/falconcrest_iplist.txt +345 -0
- data/spec/feeds/data/feodo_domainlist.txt +18 -0
- data/spec/feeds/data/feodo_iplist.txt +20 -0
- data/spec/feeds/data/h3x_asprox.txt +20 -0
- data/spec/feeds/data/hosts-file_hphostspartial_domainlist.txt +24 -0
- data/spec/feeds/data/infiltrated_iplist.txt +16 -0
- data/spec/feeds/data/infiltrated_vabl_iplist.txt +33 -0
- data/spec/feeds/data/isc_suspicious_high_domainlist.txt +26 -0
- data/spec/feeds/data/isc_suspicious_low_domainlist.txt +34 -0
- data/spec/feeds/data/isc_suspicious_medium_domainlist.txt +32 -0
- data/spec/feeds/data/malc0de_domainlist.txt +18 -0
- data/spec/feeds/data/malc0de_iplist.txt +14 -0
- data/spec/feeds/data/malwaredomainlist-url-reputation.txt +8 -0
- data/spec/feeds/data/malwaredomains_domainlist.txt +24 -0
- data/spec/feeds/data/malwaredomains_dyndns_domainlist.txt +34 -0
- data/spec/feeds/data/malwaredomains_justdomains_domainlist.txt +18 -0
- data/spec/feeds/data/mirc_domainlist.txt +31 -0
- data/spec/feeds/data/multiproxy_iplist.txt +15 -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/openphish-url-reputation.txt +16 -0
- data/spec/feeds/data/packetmail_perimeterbad-ip_reputation.txt +44 -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/sigmaproject_atma.return.gz +0 -0
- data/spec/feeds/data/sigmaproject_spyware.return.gz +0 -0
- data/spec/feeds/data/sigmaproject_webexploit.return.gz +0 -0
- data/spec/feeds/data/snort_bpf-ip_reputation.txt +16 -0
- data/spec/feeds/data/spyeye_domainlist.txt +16 -0
- data/spec/feeds/data/spyeye_iplist.txt +19 -0
- data/spec/feeds/data/steeman-ip-reputation.txt +13 -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/trustedsec-ip-reputation.txt +12 -0
- data/spec/feeds/data/valid.json +2908 -0
- data/spec/feeds/data/virbl-ip_reputation.txt +14 -0
- data/spec/feeds/data/vxvault-url-reputation.txt +15 -0
- data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
- data/spec/feeds/data/yoyo_adservers.txt +25 -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 +39 -0
- data/spec/feeds/falconcrest-ip_reputation_spec.rb +39 -0
- data/spec/feeds/feodo-domain_reputation_spec.rb +47 -0
- data/spec/feeds/feodo-ip_reputation_spec.rb +47 -0
- data/spec/feeds/h3x_asprox-ip_reputation_spec.rb +50 -0
- data/spec/feeds/hosts-file_hphostspartial-domain_reputation_spec.rb +47 -0
- data/spec/feeds/infiltrated-ip_reputation_spec.rb +47 -0
- data/spec/feeds/infiltrated_vabl-ip_reputation_spec.rb +47 -0
- data/spec/feeds/isc_suspicious_high-domain_reputation_spec.rb +47 -0
- data/spec/feeds/isc_suspicious_low-domain_reputation_spec.rb +47 -0
- data/spec/feeds/isc_suspicious_medium-domain_reputation_spec.rb +47 -0
- data/spec/feeds/malc0de-domain_reputation_spec.rb +47 -0
- data/spec/feeds/malc0de-ip_reputation_spec.rb +47 -0
- data/spec/feeds/malwaredomainlist_url_reputation_spec.rb +50 -0
- data/spec/feeds/malwaredomains-domain_reputation_spec.rb +47 -0
- data/spec/feeds/malwaredomains_dyndns-domain_reputation_spec.rb +47 -0
- data/spec/feeds/malwaredomains_justdomains-domain_reputation_spec.rb +47 -0
- data/spec/feeds/mirc-domain_reputation_spec.rb +47 -0
- data/spec/feeds/multiproxy-ip_reputation_spec.rb +47 -0
- data/spec/feeds/nothink_irc-ip_reputation_spec.rb +47 -0
- data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +47 -0
- data/spec/feeds/openbl-ip_reputation_spec.rb +47 -0
- data/spec/feeds/openphish_url_reputation_spec.rb +50 -0
- data/spec/feeds/packetmail_perimeterbad-ip_reputation_spec.rb +47 -0
- data/spec/feeds/palevo-domain_reputation_spec.rb +47 -0
- data/spec/feeds/palevo-ip_reputation_spec.rb +47 -0
- data/spec/feeds/phishtank_spec.rb +41 -0
- data/spec/feeds/sigmaproject_atma_spec.rb +62 -0
- data/spec/feeds/sigmaproject_spyware_spec.rb +63 -0
- data/spec/feeds/sigmaproject_webexploit_spec.rb +62 -0
- data/spec/feeds/snort_bpf-ip_reputation_spec.rb +47 -0
- data/spec/feeds/spyeye-domain_reputation_spec.rb +47 -0
- data/spec/feeds/spyeye-ip_reputation_spec.rb +47 -0
- data/spec/feeds/steeman-ip_reputation_spec.rb +50 -0
- data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +47 -0
- data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +47 -0
- data/spec/feeds/trustedsec-ip_reputation_spec.rb +47 -0
- data/spec/feeds/virbl-ip_reputation_spec.rb +47 -0
- data/spec/feeds/vxvault_url_reputation_spec.rb +50 -0
- data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +47 -0
- data/spec/feeds/yoyo_adservers_spec.rb +47 -0
- data/spec/feeds/zeus-domain_reputation_spec.rb +47 -0
- data/spec/feeds/zeus-ip_reputation_spec.rb +47 -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 +54 -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 +31 -0
- data/spec/support/factories/feed.rb +59 -0
- data/spec/support/factories/feed_builder.rb +65 -0
- data/spec/support/factories/feed_registry.rb +8 -0
- data/spec/support/factories/ipv4.rb +36 -0
- data/spec/support/factories/output.rb +11 -0
- data/spec/support/factories/record.rb +17 -0
- data/spec/support/factories/url.rb +34 -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/feed_runner_observer.rb +136 -0
- data/spec/support/shared/feeds.rb +233 -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/model/collection.rb +164 -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 +148 -0
- data/spec/threatinator/actions/run/action_spec.rb +106 -0
- data/spec/threatinator/actions/run/config_spec.rb +39 -0
- data/spec/threatinator/actions/run/coverage_observer_spec.rb +151 -0
- data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
- data/spec/threatinator/actions/run/status_observer_spec.rb +86 -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 +123 -0
- data/spec/threatinator/event_spec.rb +254 -0
- data/spec/threatinator/event_spec.rb.new +319 -0
- data/spec/threatinator/feed_builder_spec.rb +633 -0
- data/spec/threatinator/feed_registry_spec.rb +198 -0
- data/spec/threatinator/feed_runner_spec.rb +372 -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/logger_spec.rb +29 -0
- data/spec/threatinator/model/observables/fqdn_collection_spec.rb +41 -0
- data/spec/threatinator/model/observables/ipv4_collection_spec.rb +36 -0
- data/spec/threatinator/model/observables/ipv4_spec.rb +75 -0
- data/spec/threatinator/model/observables/url_collection_spec.rb +45 -0
- data/spec/threatinator/model/validations/type_spec.rb +37 -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 +47 -0
- data/spec/threatinator/plugins/output/null_spec.rb +17 -0
- data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -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 +674 -0
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
@event_types = opts.delete(:event_types) || [:uknown]
|
|
23
|
+
@parser_block = opts.delete(:parser_block)
|
|
24
|
+
|
|
25
|
+
@parser_builder = opts.delete(:parser_builder)
|
|
26
|
+
@fetcher_builder = opts.delete(:fetcher_builder)
|
|
27
|
+
@filter_builders = opts.delete(:filter_builders) || []
|
|
28
|
+
@decoder_builders = opts.delete(:decoder_builders) || []
|
|
29
|
+
validate!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def provider
|
|
33
|
+
@provider.dup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def name
|
|
37
|
+
@name.dup
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def event_types
|
|
41
|
+
@event_types.dup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parser_block
|
|
45
|
+
@parser_block
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fetcher_builder
|
|
49
|
+
@fetcher_builder
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parser_builder
|
|
53
|
+
@parser_builder
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def filter_builders
|
|
57
|
+
@filter_builders.dup
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def decoder_builders
|
|
61
|
+
@decoder_builders.dup
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate!
|
|
65
|
+
validate_attribute!(:provider, @provider) { |x| x.kind_of?(::String) }
|
|
66
|
+
validate_attribute!(:name, @name) { |x| x.kind_of?(::String) }
|
|
67
|
+
validate_attribute!(:event_types, @event_types) { |x| x.kind_of?(::Array) }
|
|
68
|
+
validate_attribute!(:parser_block, @parser_block) { |x| x.kind_of?(::Proc) }
|
|
69
|
+
validate_attribute!(:fetcher_builder, @fetcher_builder) { |x| x.kind_of?(::Proc) }
|
|
70
|
+
validate_attribute!(:parser_builder, @parser_builder) { |x| x.kind_of?(::Proc) }
|
|
71
|
+
validate_attribute!(:filter_builders, @filter_builders) do |x|
|
|
72
|
+
x.kind_of?(::Array) &&
|
|
73
|
+
x.all? { |e| e.kind_of?(::Proc) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
validate_attribute!(:decoder_builders, @decoder_builders) do |x|
|
|
77
|
+
x.kind_of?(::Array) &&
|
|
78
|
+
x.all? { |e| e.kind_of?(::Proc) }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def validate_attribute!(name, val, &block)
|
|
83
|
+
unless block.call(val) == true
|
|
84
|
+
raise Threatinator::Exceptions::InvalidAttributeError.new("Invalid attribute (#{name}). Got: #{val.inspect}")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
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 event_types(event_types=[:notlabeled])
|
|
27
|
+
@event_types ||= event_types
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fetch_http(url, opts = {})
|
|
32
|
+
opts[:url] = url
|
|
33
|
+
@fetcher_builder = lambda do
|
|
34
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
35
|
+
Threatinator::Fetchers::Http.new(opts_dup)
|
|
36
|
+
end
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_xml(pattern_string, opts = {}, &block)
|
|
41
|
+
@parser_builder = lambda do
|
|
42
|
+
pattern = Threatinator::Parsers::XML::Pattern.new(pattern_string)
|
|
43
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
44
|
+
opts_dup[:pattern] = pattern
|
|
45
|
+
Threatinator::Parsers::XML::Parser.new(opts_dup, &block)
|
|
46
|
+
end
|
|
47
|
+
@parser_block = block
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_json(opts = {}, &block)
|
|
52
|
+
@parser_builder = lambda do
|
|
53
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
54
|
+
Threatinator::Parsers::JSON::Parser.new(opts_dup, &block)
|
|
55
|
+
end
|
|
56
|
+
@parser_block = block
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def parse_eachline(opts = {}, &block)
|
|
61
|
+
@parser_builder = lambda do
|
|
62
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
63
|
+
Threatinator::Parsers::Getline::Parser.new(opts_dup, &block)
|
|
64
|
+
end
|
|
65
|
+
@parser_block = block
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def parse_csv(opts = {}, &block)
|
|
70
|
+
@parser_builder = lambda do
|
|
71
|
+
opts_dup = Marshal.load(Marshal.dump(opts))
|
|
72
|
+
Threatinator::Parsers::CSV::Parser.new(opts_dup, &block)
|
|
73
|
+
end
|
|
74
|
+
@parser_block = block
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Specify a block filter for the parser
|
|
79
|
+
def filter(&block)
|
|
80
|
+
@filter_builders ||= []
|
|
81
|
+
@filter_builders << lambda { Threatinator::Filters::Block.new(block) }
|
|
82
|
+
self
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Filter out whitespace lines. Only works on line-based text.
|
|
86
|
+
def filter_whitespace
|
|
87
|
+
@filter_builders ||= []
|
|
88
|
+
@filter_builders << lambda { Threatinator::Filters::Whitespace.new }
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Filter out whitespace lines. Only works on line-based text.
|
|
93
|
+
def filter_comments
|
|
94
|
+
@filter_builders ||= []
|
|
95
|
+
@filter_builders << lambda { Threatinator::Filters::Comments.new }
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Add the Gzip decoder
|
|
100
|
+
def decode_gzip
|
|
101
|
+
decoder_builders << lambda { Threatinator::Decoders::Gzip.new }
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
alias_method :extract_gzip, :decode_gzip
|
|
105
|
+
alias_method :gunzip, :decode_gzip
|
|
106
|
+
|
|
107
|
+
def decoder_builders
|
|
108
|
+
@decoder_builders ||= []
|
|
109
|
+
end
|
|
110
|
+
private :decoder_builders
|
|
111
|
+
|
|
112
|
+
def build
|
|
113
|
+
Feed.new(
|
|
114
|
+
:provider => @provider,
|
|
115
|
+
:name => @name,
|
|
116
|
+
:event_types => @event_types || [:unknown],
|
|
117
|
+
:parser_block => @parser_block,
|
|
118
|
+
:fetcher_builder => @fetcher_builder,
|
|
119
|
+
:parser_builder => @parser_builder,
|
|
120
|
+
:filter_builders => @filter_builders,
|
|
121
|
+
:decoder_builders => decoder_builders
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Loads the provided file, and generates a builder from it.
|
|
126
|
+
# @param [String] filename The name of the file to read the feed from
|
|
127
|
+
# @raise [FeedFileNotFoundError] if the file is not found
|
|
128
|
+
def self.from_file(filename)
|
|
129
|
+
begin
|
|
130
|
+
filedata = File.read(filename)
|
|
131
|
+
rescue Errno::ENOENT
|
|
132
|
+
raise Threatinator::Exceptions::FeedFileNotFoundError.new(filename)
|
|
133
|
+
end
|
|
134
|
+
from_string(filedata, filename, 0)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Generates a builder from a string via eval.
|
|
138
|
+
# @param [String] str The DSL code that specifies the feed.
|
|
139
|
+
# @param [String] filename (nil) Passed to eval.
|
|
140
|
+
# @param [String] lineno (nil) Passed to eval.
|
|
141
|
+
# @raise [FeedFileNotFoundError] if the file is not found
|
|
142
|
+
# @see Kernel#eval for details on filename and lineno
|
|
143
|
+
def self.from_string(str, filename = nil, lineno = nil)
|
|
144
|
+
from_dsl do
|
|
145
|
+
args = [str, binding]
|
|
146
|
+
unless filename.nil?
|
|
147
|
+
args << filename
|
|
148
|
+
unless lineno.nil?
|
|
149
|
+
args << lineno
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
eval(*args)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Executes the block parameter within DSL scope
|
|
157
|
+
def self.from_dsl(&block)
|
|
158
|
+
Docile.dsl_eval(self.new, &block)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -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,177 @@
|
|
|
1
|
+
require 'threatinator/event_builder'
|
|
2
|
+
require 'threatinator/logging'
|
|
3
|
+
require 'observer'
|
|
4
|
+
|
|
5
|
+
module Threatinator
|
|
6
|
+
# Runs those feeds!
|
|
7
|
+
#
|
|
8
|
+
# Has the following observations:
|
|
9
|
+
# :start - start of feed parsing
|
|
10
|
+
#
|
|
11
|
+
# :start_fetch - start of fetching
|
|
12
|
+
# :end_fetch - end of fetching
|
|
13
|
+
#
|
|
14
|
+
# :start_decode - start of decoding
|
|
15
|
+
# :end_decode - end of decoding
|
|
16
|
+
#
|
|
17
|
+
# :start_parse_record - start of record parse
|
|
18
|
+
# - record - The record
|
|
19
|
+
#
|
|
20
|
+
# :record_filtered - Indicates that the record was filtered.
|
|
21
|
+
# - record - The record
|
|
22
|
+
#
|
|
23
|
+
# :record_missed - Indicates that the record was not parsed
|
|
24
|
+
# - record - The record
|
|
25
|
+
#
|
|
26
|
+
# :record_parsed - Indicates that the record WAS parsed
|
|
27
|
+
# - record - The record
|
|
28
|
+
# - events - The events that were parsed out of the
|
|
29
|
+
# record
|
|
30
|
+
#
|
|
31
|
+
# :record_error - Indicates that the record WAS parsed
|
|
32
|
+
# - record - The record
|
|
33
|
+
# - errors - An array of exceptions that caused the error
|
|
34
|
+
#
|
|
35
|
+
# :end_parse_record - when a record has been parsed
|
|
36
|
+
# - record - The record
|
|
37
|
+
#
|
|
38
|
+
# :end - completion of feed parsing
|
|
39
|
+
#
|
|
40
|
+
class FeedRunner
|
|
41
|
+
include Observable
|
|
42
|
+
include Logging
|
|
43
|
+
|
|
44
|
+
# @param [Threatinator::Feed] feed The feed that we want to run.
|
|
45
|
+
# @param [Threatinator::Output] output_formatter
|
|
46
|
+
def initialize(feed, output_formatter, opts = {})
|
|
47
|
+
@feed = feed
|
|
48
|
+
@output_formatter = output_formatter
|
|
49
|
+
@feed_filters = @feed.filter_builders.map { |x| x.call }
|
|
50
|
+
@decoders = @feed.decoder_builders.map { |x| x.call }
|
|
51
|
+
@parser_block = @feed.parser_block
|
|
52
|
+
@create_event_proc = self.method(:create_event).to_proc
|
|
53
|
+
|
|
54
|
+
@event_builder = Threatinator::EventBuilder.new(@feed.provider, @feed.name)
|
|
55
|
+
|
|
56
|
+
@total_events_built = 0
|
|
57
|
+
@event_errors = []
|
|
58
|
+
@built_events = []
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param [Hash] opts The options hash
|
|
62
|
+
# @option opts [IO-like] :io Override the fetcher by providing
|
|
63
|
+
# an IO directly.
|
|
64
|
+
# @option opts [Boolean] :skip_decoding (false) Skip all decoding if set
|
|
65
|
+
# to true. Useful for testing.
|
|
66
|
+
def run(opts = {})
|
|
67
|
+
ios = [ ]
|
|
68
|
+
logger.debug("#run starting #{@feed.provider}:#{@feed.name}") if logger.debug?
|
|
69
|
+
start = Time.now
|
|
70
|
+
changed(true); notify_observers(:start)
|
|
71
|
+
skip_decoding = !!opts.delete(:skip_decoding)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
unless io = opts.delete(:io)
|
|
75
|
+
fetcher = @feed.fetcher_builder.call()
|
|
76
|
+
changed(true); notify_observers(:start_fetch)
|
|
77
|
+
io = fetcher.fetch()
|
|
78
|
+
changed(true); notify_observers(:end_fetch)
|
|
79
|
+
else
|
|
80
|
+
logger.debug('#run Skipping fetch. IO object was provided')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ios << io
|
|
84
|
+
|
|
85
|
+
unless skip_decoding == true
|
|
86
|
+
changed(true); notify_observers(:start_decode)
|
|
87
|
+
@decoders.each do |decoder|
|
|
88
|
+
new_io = decoder.decode(io)
|
|
89
|
+
ios << new_io
|
|
90
|
+
io = new_io
|
|
91
|
+
end
|
|
92
|
+
changed(true); notify_observers(:end_decode)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
parser = @feed.parser_builder.call()
|
|
96
|
+
|
|
97
|
+
parser.run(io) do |record|
|
|
98
|
+
rr = parse_record(record)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
changed(true); notify_observers(:end)
|
|
102
|
+
|
|
103
|
+
logger.debug("#run finished #{@feed.provider}:#{@feed.name} in #{Time.now - start} seconds") if logger.debug?
|
|
104
|
+
nil
|
|
105
|
+
ensure
|
|
106
|
+
# Close all IO objects that we've seen.
|
|
107
|
+
while some_io = ios.pop
|
|
108
|
+
unless some_io.closed?
|
|
109
|
+
begin
|
|
110
|
+
some_io.close
|
|
111
|
+
rescue => e
|
|
112
|
+
#:nocov:
|
|
113
|
+
logger.warn("Failed to close IO: #{e} #{e.message}")
|
|
114
|
+
#:nocov:
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
@output_formatter.finish
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def create_event
|
|
123
|
+
@event_builder.reset
|
|
124
|
+
@event_errors.clear
|
|
125
|
+
yield(@event_builder)
|
|
126
|
+
begin
|
|
127
|
+
event = @event_builder.build
|
|
128
|
+
@total_events_built += 1
|
|
129
|
+
@built_events << event
|
|
130
|
+
rescue Threatinator::Exceptions::EventBuildError => e
|
|
131
|
+
@event_errors << e
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def parse_record(record)
|
|
136
|
+
@built_events.clear
|
|
137
|
+
events = []
|
|
138
|
+
changed(true); notify_observers(:start_parse_record, record)
|
|
139
|
+
|
|
140
|
+
if @feed_filters.any? { |filter| filter.filter?(record) }
|
|
141
|
+
changed(true); notify_observers(:record_filtered, record)
|
|
142
|
+
return
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
@parser_block.call(@create_event_proc, record)
|
|
146
|
+
|
|
147
|
+
if @event_errors.count > 0
|
|
148
|
+
changed(true); notify_observers(:record_error, record, @event_errors)
|
|
149
|
+
position = "line: #{record.line_number}, start: #{record.pos_start}, end: #{record.pos_end}"
|
|
150
|
+
messages = @event_errors.map { |e| e.to_s }.join(', ')
|
|
151
|
+
logger.debug("Error generating event from record (#{position}): #{messages}")
|
|
152
|
+
elsif @built_events.count == 0
|
|
153
|
+
changed(true); notify_observers(:record_missed, record)
|
|
154
|
+
position = "line: #{record.line_number}, start: #{record.pos_start}, end: #{record.pos_end}"
|
|
155
|
+
logger.debug("Expected event to be generated, but got none from record (#{position})")
|
|
156
|
+
else
|
|
157
|
+
@built_events.each do |event|
|
|
158
|
+
events << event
|
|
159
|
+
@output_formatter.handle_event(event)
|
|
160
|
+
end
|
|
161
|
+
changed(true); notify_observers(:record_parsed, record, events)
|
|
162
|
+
end
|
|
163
|
+
return
|
|
164
|
+
ensure
|
|
165
|
+
changed(true); notify_observers(:end_parse_record, record)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Runs a feed
|
|
169
|
+
# @param [Threatinator::Feed] feed The feed to run
|
|
170
|
+
# @param [Threatinator::Output] output The output instance
|
|
171
|
+
# @param [Hash] run_opts Options passed to #run. See #run .
|
|
172
|
+
def self.run(feed, output, run_opts = {})
|
|
173
|
+
self.new(feed, output).run(run_opts)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
end
|
|
177
|
+
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,50 @@
|
|
|
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,
|
|
28
|
+
ssl_verifypeer: false,
|
|
29
|
+
followlocation: true,
|
|
30
|
+
forbid_reuse: true
|
|
31
|
+
)
|
|
32
|
+
request.on_headers do |response|
|
|
33
|
+
if response.response_code != 200
|
|
34
|
+
|
|
35
|
+
raise Threatinator::Exceptions::FetchFailed.new("Request failed!")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
request.on_body do |chunk|
|
|
39
|
+
tempfile.write(chunk)
|
|
40
|
+
end
|
|
41
|
+
# Run it
|
|
42
|
+
request.run
|
|
43
|
+
# Reset the IO to the beginning of the file
|
|
44
|
+
tempfile.rewind
|
|
45
|
+
tempfile
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
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,66 @@
|
|
|
1
|
+
require 'log4r'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
module Logger
|
|
5
|
+
# The log levels don't get defined until the first time a logger is
|
|
6
|
+
# initialized. So, we call the root logger once just to get this to happen.
|
|
7
|
+
Log4r::RootLogger.instance
|
|
8
|
+
|
|
9
|
+
def self.logger_for(name)
|
|
10
|
+
::Log4r::Logger[name] || ::Log4r::Logger.new(name)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.default_logger
|
|
14
|
+
return @logger unless @logger.nil?
|
|
15
|
+
|
|
16
|
+
@logger = logger_for('Threatinator')
|
|
17
|
+
formatter = ::Log4r::PatternFormatter.new(:pattern => '[%d] %l %C: %M')
|
|
18
|
+
|
|
19
|
+
console_outputter = ::Log4r::StderrOutputter.new('console', formatter: formatter)
|
|
20
|
+
@logger.add console_outputter
|
|
21
|
+
|
|
22
|
+
@logger.level = ::Log4r::INFO
|
|
23
|
+
@logger
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param [Threatinator::Config::Logger] config Logging configuration object
|
|
27
|
+
def self.configure_logger(config)
|
|
28
|
+
if config.level
|
|
29
|
+
if l = self.levels.index(config.level)
|
|
30
|
+
default_logger.level = l
|
|
31
|
+
else
|
|
32
|
+
default_logger.warn("Ignoring unknown logging level: #{config.level.inspect}.")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.level
|
|
38
|
+
default_logger.level
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.level=(l)
|
|
42
|
+
default_logger.level = l
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.level_string
|
|
46
|
+
levels[level]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.levels
|
|
50
|
+
default_logger.levels
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Initializes the default logger. This allows us to pull the logger levels.
|
|
54
|
+
self.default_logger()
|
|
55
|
+
|
|
56
|
+
module Levels
|
|
57
|
+
ALL = ::Log4r::ALL
|
|
58
|
+
DEBUG = ::Log4r::DEBUG
|
|
59
|
+
INFO = ::Log4r::INFO
|
|
60
|
+
WARN = ::Log4r::WARN
|
|
61
|
+
ERROR = ::Log4r::ERROR
|
|
62
|
+
FATAL = ::Log4r::FATAL
|
|
63
|
+
OFF = ::Log4r::OFF
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'threatinator/logger'
|
|
2
|
+
|
|
3
|
+
module Threatinator
|
|
4
|
+
# Mixin for mixing logging facilities into classes.
|
|
5
|
+
module Logging
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend(LoggingClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def logger
|
|
11
|
+
@logger ||= self.class.logger
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module LoggingClassMethods
|
|
16
|
+
def logger
|
|
17
|
+
@logger ||= Threatinator::Logger.logger_for(self.name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|