#!/usr/bin/env ruby # frozen_string_literal: false require 'cgi' require 'optparse' require 'pwn' require 'yaml' opts = {} OptionParser.new do |options| options.banner = "USAGE: #{$PROGRAM_NAME} [opts] " options.on('-cCONFIG', '--config=CONFG', '') do |g| opts[:config] = g end options.on('-pID', '--parent-group-id=ID', '') do |p| opts[:parent_group_id] = p end options.on('-sFILE', '--scan=FILE', '') do |f| opts[:target_file] = f end options.on('-rPATH', '--report=PATH', '') do |r| opts[:report_path] = r end options.on('-q', '--queue-timeout', '') do |q| opts[:queue_timeout] = q end options.on('-a', '--scan-attempts', '') do |a| opts[:scan_attempts] = a end options.on('-R', '--report-only', '') do |o| opts[:report_only] = o end options.on('-tTYPE', '--report-type=TYPE', '') do |t| opts[:report_type] = t end options.on('-vVERSION', '--version=VERSION', '') do |v| opts[:version] = v end end.parse! if opts.empty? puts `#{$PROGRAM_NAME} --help` exit 1 end abort_total = 0 begin pwn_provider = 'ruby-gem' pwn_provider = ENV.fetch('PWN_PROVIDER') if ENV.keys.any? { |s| s == 'PWN_PROVIDER' } config = opts[:config] raise "ERROR: BDBA YAML Config File Not Found: #{config}" unless File.exist?(config) yaml_config = YAML.load_file(config, symbolize_names: true) token = yaml_config[:token] raise "ERROR: BDBA Token Not Found: #{token}" if token.nil? parent_group_id = opts[:parent_group_id] raise "ERROR: BDBA Parent Group ID Not Provided: #{parent_group_id}" if parent_group_id.nil? target_file = opts[:target_file] raise "ERROR: BDBA Target File Not Found: #{target_file}" unless File.exist?(target_file) report_path = opts[:report_path] raise "ERROR: BDBA Report Path Not Provided: #{report_path}" if report_path.nil? queue_timeout = opts[:queue_timeout] ||= 5_400 scan_attempts = opts[:scan_attempts] ||= 3 report_only = opts[:report_only] ||= false report_type_str = opts[:report_type] ||= 'csv_vulns' report_type = report_type_str.to_s.to_sym version = opts[:version] unless report_only puts "Uploading/Scanning: #{target_file}" PWN::Plugins::BlackDuckBinaryAnalysis.upload_file( token: token, file: target_file, group_id: parent_group_id, version: version ) end scan_progress_resp = {} scan_progress_busy_duration = 0 loop do scan_progress_resp = PWN::Plugins::BlackDuckBinaryAnalysis.get_apps_by_group( token: token, group_id: parent_group_id ) break if scan_progress_resp[:products].none? { |p| p[:status] == 'B' } || report_only # Cancel queued scan if it's been queued for more than 90 minutes if scan_progress_busy_duration > queue_timeout.to_i abort_total += 1 puts "Scan Queued for More than #{queue_timeout} Seconds." puts "Aborting and Re-Queuing Attempt #{abort_total} of #{scan_attempts}..." scan_progress_resp[:products].select { |p| p[:status] == 'B' }.each do |p| puts "Abort Queued Scan: #{p[:name]}" PWN::Plugins::BlackDuckBinaryAnalysis.abort_product_scan( token: token, product_id: p[:product_id] ) end retry if abort_total <= scan_attempts.to_i raise "ERROR: BDBA Scan Aborted: #{target_file}" end 10.times do print '.' sleep 1 end scan_progress_busy_duration += 10 end product_id = scan_progress_resp[:products].find { |p| p[:name] == CGI.escape(File.basename(target_file)) }[:product_id] scan_report_resp = PWN::Plugins::BlackDuckBinaryAnalysis.generate_product_report( token: token, product_id: product_id, type: report_type, output_path: report_path ) puts "\nReport Saved to: #{report_path}" rescue SystemExit, Interrupt puts "\nGoodbye." rescue StandardError => e raise e end