# frozen_string_literal: true require 'norad_cli/support/results_server.rb' require 'net/http' require 'securerandom' require 'rainbow' module NoradCli class SecTestContainer attr_accessor :container attr_accessor :assessment_id attr_accessor :sectest_image attr_accessor :results_server def initialize(sectest_name, options) # Generate a random assessment id @assessment_id = SecureRandom.hex(32) @sectest_image = "#{options['registry']}/#{sectest_name}:#{options[:version]}" # Create a results server @results_server = NoradCli::ResultsServer.new('docker-images-test-results-server') ENV['ENABLE_LOGS'] = 'true' env = [ 'NORAD_ROOT=http://results:3000', %(ASSESSMENT_PATHS=[{"id":"singletest", "assessment": "/results/#{@assessment_id}"}]), 'NORAD_SECRET=1234' ] # Create the container @container = Docker::Container.create(Image: @sectest_image, Cmd: prog_args(sectest_name, options), Env: env, HostConfig: { Links: ["#{@results_server.container.id}:results"] }) end # Format the prog_args appropriately for the container def prog_args(sectest_name, options) # Grab the program arguments (minus other function options) # Options is a Thor::CoreExt::HashWithIndifferentAccess (except does not work) prog_arg_hash = options.each_with_object({}) do |(k, v), hsh| hsh[k.to_sym] = v unless k == 'debug' || k == 'registry' || k == 'version' end # Load the prog_arg format sectest_options ||= YAML.safe_load(File.read("sectests/#{sectest_name}/manifest.yml")) # Load an ssh key if necess prog_arg_hash[:ssh_key] = load_ssh_key(prog_arg_hash[:ssh_key]) if prog_arg_hash.key?(:ssh_key) # Fill out the prog_args and return begin format(sectest_options['prog_args'], prog_arg_hash).split(' ') rescue KeyError puts Rainbow('Error: The containers required arguments were not set.').red puts Rainbow("Arguments in %{} should be set: #{sectest_options['prog_args']}").red puts Rainbow("Arguments given: #{prog_arg_hash}").red puts Rainbow("Run 'norad sectest execute #{sectest_name} -h' to see how to set arguments!").red puts Rainbow('Exiting...').red exit(1) end end def start # Start the results server container @results_server.start # Start the sectest container @container.start @container.wait(60 * 10) end def output(target) # Output container logs for debugging @container.stop c_state = @container.json['State'] # Print the entire state regardless of error or not to aid in debugging puts Rainbow("[DEBUG] Container #{@sectest_image}'s Final State").green puts Rainbow('-------------------------').green c_state.each do |key, value| puts Rainbow("#{key}: #{value}").green end puts Rainbow("\n[DEBUG] Logs for target #{@sectest_image} run against #{target}:").green # Print logs regardless of ExitCode puts Rainbow(@container.logs(stdout: true, stderr: true)).green end def results # Get the results url = "http://localhost:#{@results_server.host_port}/results?assessment_id=#{@assessment_id}" uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Get.new(uri) response = http.request(request) if response.code == '200' response.body else puts Rainbow('Error retrieving results\nExiting...').red shutdown exit(1) end end def shutdown # Stop the sectest image container and delete @container.stop @container.delete(force: true) # Cleanup/Garbage collect the results server @results_server.shutdown end private # Replace ssh key file with encoded ssh key def load_ssh_key(ssh_key_file) if File.exist?(ssh_key_file) Base64.strict_encode64(File.read(ssh_key_file)) else puts Rainbow("Error: SSH Key file: #{ssh_key_file} does not exist!\nExiting..\n").red exit(1) end end end end