Top Level Namespace

Defined Under Namespace

Modules: NoradCli, SeedGenerator, TestServer Classes: ApplicationController, ApplicationRecord, BaseMachine, CreateResults, Manifest, Readme, Repo, Result, ResultsController, ResultsServer, Sectest

Constant Summary collapse

AssessmentHelpers =
Module.new do
  MAX_SLEEP_TIME = 30
  SLEEP_INTERVAL = 3

  define_method  :get_scans do
    %w(vulnerable secure)
  end

  define_method :assessment_name do
    name.split(/(?=[A-Z])|(?<=[a-z])(\d+)/).map { |e| e.downcase }.join('-')
  end

  define_method :manifest_file do
    assessment_path = @parent.nil? ? assessment_name : "#{@parent}/variants/#{assessment_name}"
    "./sectests/#{assessment_path}/manifest.yml"
  end

  define_method :options do
    @options ||= YAML.load(File.read(manifest_file))
  end

  define_method :image_name do
    "#{options['registry']}/#{options['name']}:#{options['version']}"
  end

  define_method :config_hash do
    return default_test_config if respond_to?(:default_test_config)
    options['default_config'].each_with_object({}) do |(k, v), h|
      h[k.to_sym] = v
    end
  end

  define_method :args do |target|
    ssh_vals = options['prog_args']['ssh_key'] ? load_ssh_args : {}
    format(options['prog_args'], config_hash.merge({target: target}).merge(ssh_vals)).split(' ')
  end

  define_method :load_ssh_args do
    { ssh_user: 'testuser', ssh_key: Base64.strict_encode64(File.read(NoradCli.ssh_key_path)) }
  end

  define_method :machine_ready? do |machine, image_name|
    if @verify_machine_ready
      config_status =  machine.exec(['cat', '/tmp/status']).to_s
      unless config_status.match(/ready_to_test/)
        puts "machine:#{image_name} not ready_to_test current status:#{config_status}"
        return false
      end
    end
    true
  end

  define_method :machine_running? do |machine, image_name|
    if !machine.json['State']['Running']
      puts "Target machine: #{image_name} failed to start."
      return false
    end
    true
  end

  define_method :start_target_machine do |image_name|
    elapsed_time = 0
    target = image_name.split('-')[-1].to_sym
    h_config = nil
    h_config = @host_config[target] if @host_config
    machine = Docker::Container.create(Image: image_name, HostConfig: h_config )
    machine.start
    sleep SLEEP_INTERVAL
    until (machine_running?(machine, image_name) && machine_ready?(machine, image_name)) || elapsed_time > MAX_SLEEP_TIME
      sleep SLEEP_INTERVAL # sleep rather than wait since we are daemonizing a container
      elapsed_time += SLEEP_INTERVAL
    end
    machine
  end

  define_method :log_machine_not_running do |machine, image_name|
    puts '*' * 80
    puts "* target docker container for #{image_name} not running"
    puts '*' * 80
    pp machine.json
    puts "******************** logs  #{image_name} *****************************"
    machine.streaming_logs(stdout: true, stderr: true) { |stream, chunk| puts "#{stream}: #{chunk}" }
  end

  define_method :stop_target_machine do |machine|
    machine.stop
    # Do not delete targets if debugging enabled
    if !ENV['ENABLE_NORAD_DEBUG']
      machine.delete(force: true)
    end
    machine.id
  end

  define_method :scan_machine do |type|
    image_name = "#{assessment_name}-#{type}"
    machine = start_target_machine(image_name)
    target_name = type
    target_name = @target_host if @target_host
    scan(target_name, machine.id) if machine_running?(machine, image_name)  && machine_ready?(machine, image_name)
    log_machine_not_running(machine, image_name) unless machine_running?(machine, image_name)
    stop_target_machine(machine)
  end

  define_method :scan_base do
    assessment_id = scan('base', base_machine.container.id)
    assessment_id
  end

  define_method :scan do |target, target_id|
    assessment_id = target == 'base' ? SecureRandom.hex(32) : target_id
    env = [
      "NORAD_ROOT=http://results:3000",
      %Q{ASSESSMENT_PATHS=[{"id":"#{target}", "assessment": "/results/#{assessment_id}"}]},
      "NORAD_SECRET=1234"
    ]
    c = Docker::Container.create({
      Image: image_name,
      Cmd: args(target),
      Env: env,
      HostConfig: { Links: ["#{target_id}:#{target}", "#{results_server.container.id}:results"] },
    })

    c.start
    c.wait(60 * 10)

    # Output container logs for debugging
    if ENV['ENABLE_LOGS']
      c.stop
      c_state = c.json['State']

      # Print the entire state regardless of error or not to aid in debugging
      puts "[DEBUG] Container #{image_name}'s Final State"
      puts '-------------------------'
      c_state.each do |key, value|
        puts "#{key}: #{value}"
      end

      puts "\n[DEBUG] Logs for target \"#{image_name}\" run against #{target}:"

      # Print logs regardless of ExitCode
      puts c.logs(stdout: true, stderr: true)

    end

    c.delete(force: true)
    assessment_id
  end

  define_method :retrieve_results do |id|
    url = "http://localhost:#{results_server.host_port}/results?assessment_id=#{id}"
    uri = URI(url)
    JSON.parse(Net::HTTP.get(uri))
  end
end