class InspecTools::Inspec

Constants

DATA_NOT_FOUND_MESSAGE

Public Class Methods

new(inspec_json, metadata = {}) click to toggle source
# File lib/inspec_tools/inspec.rb, line 18
def initialize(inspec_json, metadata = {})
  @json = JSON.parse(inspec_json)
  @metadata = metadata
end

Public Instance Methods

to_ckl(title = nil, date = nil, cklist = nil) click to toggle source
# File lib/inspec_tools/inspec.rb, line 23
def to_ckl(title = nil, date = nil, cklist = nil)
  @data = Utils::InspecUtil.parse_data_for_ckl(@json)
  @platform = Utils::InspecUtil.get_platform(@json)
  @title = generate_title title, @json, date
  @cklist = cklist
  @checklist = HappyMapperTools::StigChecklist::Checklist.new
  if @cklist.nil?
    generate_ckl
  else
    update_ckl
  end
  @checklist.to_xml.encode('UTF-8').gsub('<?xml version="1.0"?>', '<?xml version="1.0" encoding="UTF-8"?>').chomp
end
to_csv() click to toggle source

converts an InSpec JSON to a CSV file

# File lib/inspec_tools/inspec.rb, line 52
def to_csv
  @data = {}
  @data['controls'] = []
  get_all_controls_from_json(@json)
  data = inspec_json_to_array(@data)
  CSV.generate do |csv|
    data.each do |row|
      csv << row
    end
  end
end
to_xccdf(attributes, verbose = false) click to toggle source

Convert Inspec result data to XCCDF

@param attributes [Hash] Optional input attributes @return [String] XML formatted String

# File lib/inspec_tools/inspec.rb, line 41
def to_xccdf(attributes, verbose = false)
  data = parse_data_for_xccdf(@json)
  @verbose = verbose
  @benchmark = HappyMapperTools::Benchmark::Benchmark.new

  to_xml(@metadata, attributes, data)
end

Private Instance Methods

build_benchmark_header(attributes) click to toggle source
# File lib/inspec_tools/inspec.rb, line 96
def build_benchmark_header(attributes)
  @benchmark.title = attributes['benchmark.title']
  @benchmark.id = attributes['benchmark.id']
  @benchmark.description = attributes['benchmark.description']
  @benchmark.version = attributes['benchmark.version']
  @benchmark.xmlns = 'http://checklists.nist.gov/xccdf/1.1'

  @benchmark.status = HappyMapperTools::Benchmark::Status.new
  @benchmark.status.status = attributes['benchmark.status']
  @benchmark.status.date = attributes['benchmark.status.date']

  if attributes['benchmark.notice.id']
    @benchmark.notice = HappyMapperTools::Benchmark::Notice.new
    @benchmark.notice.id = attributes['benchmark.notice.id']
  end

  if attributes['benchmark.plaintext'] || attributes['benchmark.plaintext.id']
    @benchmark.plaintext = HappyMapperTools::Benchmark::Plaintext.new
    @benchmark.plaintext.plaintext = attributes['benchmark.plaintext']
    @benchmark.plaintext.id = attributes['benchmark.plaintext.id']
  end

  @benchmark.reference = HappyMapperTools::Benchmark::ReferenceBenchmark.new
  @benchmark.reference.href = attributes['reference.href']
  @benchmark.reference.dc_publisher = attributes['reference.dc.publisher']
  @benchmark.reference.dc_source = attributes['reference.dc.source']
end
build_groups(attributes, data) click to toggle source

Translate join of Inspec results and input attributes to XCCDF Groups

# File lib/inspec_tools/inspec.rb, line 125
def build_groups(attributes, data)
  group_array = []
  data['controls'].each do |control|
    group = HappyMapperTools::Benchmark::Group.new
    group.id = control['id']
    group.title = control['gtitle']
    group.description = "<GroupDescription>#{control['gdescription']}</GroupDescription>" if control['gdescription']

    group.rule = HappyMapperTools::Benchmark::Rule.new
    group.rule.id = control['rid']
    group.rule.severity = control['severity']
    group.rule.weight = control['rweight']
    group.rule.version = control['rversion']
    group.rule.title = control['title'].tr("\n", ' ') if control['title']
    group.rule.description = "<VulnDiscussion>#{control['desc']}</VulnDiscussion><FalsePositives></FalsePositives><FalseNegatives></FalseNegatives><Documentable>false</Documentable><Mitigations>#{control['rationale']}</Mitigations><SeverityOverrideGuidance></SeverityOverrideGuidance><PotentialImpacts></PotentialImpacts><ThirdPartyTools></ThirdPartyTools><MitigationControl></MitigationControl><Responsibility></Responsibility><IAControls></IAControls>"

    if ['reference.dc.publisher', 'reference.dc.title', 'reference.dc.subject', 'reference.dc.type', 'reference.dc.identifier'].any? { |a| attributes.key?(a) }
      group.rule.reference = build_rule_reference(attributes)
    end

    group.rule.ident = build_rule_idents(control['cci']) if control['cci']
    group.rule.ident += build_rule_idents(control['legacy']) if control['legacy']

    group.rule.fixtext = HappyMapperTools::Benchmark::Fixtext.new
    group.rule.fixtext.fixref = control['fix_id']
    group.rule.fixtext.fixtext = control['fix']

    group.rule.fix = build_rule_fix(control['fix_id']) if control['fix_id']

    group.rule.check = HappyMapperTools::Benchmark::Check.new
    group.rule.check.system = control['checkref']

    # content_ref is optional for schema compliance
    if attributes['content_ref.name'] || attributes['content_ref.href']
      group.rule.check.content_ref = HappyMapperTools::Benchmark::ContentRef.new
      group.rule.check.content_ref.name = attributes['content_ref.name']
      group.rule.check.content_ref.href = attributes['content_ref.href']
    end

    group.rule.check.content = control['check']

    group_array << group
  end
  @benchmark.group = group_array
end
build_rule_fix(fix_id) click to toggle source

Contruct a Rule / RuleResult fix element with the provided id.

# File lib/inspec_tools/inspec.rb, line 186
def build_rule_fix(fix_id)
  HappyMapperTools::Benchmark::Fix.new.tap { |f| f.id = fix_id }
end
build_rule_idents(idents) click to toggle source

Construct rule identifiers for rule @param idents [Array]

# File lib/inspec_tools/inspec.rb, line 192
def build_rule_idents(idents)
  raise "#{idents} is not an Array type." unless idents.is_a?(Array)

  # Each rule identifier is a different element
  idents.map do |identifier|
    HappyMapperTools::Benchmark::Ident.new identifier
  end
end
build_rule_reference(attributes) click to toggle source

Contruct a Rule reference element

# File lib/inspec_tools/inspec.rb, line 202
def build_rule_reference(attributes)
  reference = HappyMapperTools::Benchmark::ReferenceGroup.new
  reference.dc_publisher = attributes['reference.dc.publisher']
  reference.dc_title = attributes['reference.dc.title']
  reference.dc_subject = attributes['reference.dc.subject']
  reference.dc_type = attributes['reference.dc.type']
  reference.dc_identifier = attributes['reference.dc.identifier']
  reference
end
build_test_results(metadata, data) click to toggle source

Construct a Benchmark Testresult from Inspec data. This must be called after all XML processing has occurred for profiles and groups. @param metadata [Hash] @return [TestResult]

# File lib/inspec_tools/inspec.rb, line 175
def build_test_results(metadata, data)
  test_result = HappyMapperTools::Benchmark::TestResult.new
  test_result.version = @benchmark.version
  test_result = populate_remark(test_result, data)
  test_result = populate_target_facts(test_result, metadata)
  test_result = populate_identity(test_result, metadata)
  test_result = populate_results(test_result, data)
  populate_score(test_result, @benchmark.group)
end
combine_results(rule_results) click to toggle source

Combines rule results with the same result into a single rule result.

# File lib/inspec_tools/inspec.rb, line 400
def combine_results(rule_results)
  return rule_results.first if rule_results.size == 1

  # Can combine, result, idents (duplicate, take first instance), instance - combine into an array removing duplicates
  # check.content - Only one value allowed, combine by joining with line feed. Prior to, make sure all values are unique.

  rule_result = HappyMapperTools::Benchmark::RuleResultType.new
  rule_result.idref = rule_results.first.idref
  rule_result.severity = rule_results.first.severity
  # Take latest time
  rule_result.time = rule_results.reduce(rule_results.first.time) { |time, r| time > r.time ? time : r.time }
  rule_result.weight = rule_results.first.weight

  rule_result.result = rule_results.first.result
  rule_result.message = rule_results.reduce([]) { |messages, r| r.message ? messages.push(r.message) : messages }
  rule_result.instance = rule_results.reduce([]) { |instances, r| r.instance ? instances.push(r.instance) : instances }.join("\n")

  rule_result.ident = rule_results.first.ident
  rule_result.fix = rule_results.first.fix

  if rule_results.first.check
    rule_result.check = HappyMapperTools::Benchmark::Check.new
    rule_result.check.system = rule_results.first.check.system
    rule_result.check.content = rule_results.map { |r| r.check.content }.join("\n")
  end

  rule_result
end
create_stig_data_element(attribute, control) click to toggle source
# File lib/inspec_tools/inspec.rb, line 712
def create_stig_data_element(attribute, control)
  return HappyMapperTools::StigChecklist::StigData.new(attribute, control[attribute.downcase.to_sym]) unless control[attribute.downcase.to_sym].nil?
end
end_time(start, duration) click to toggle source

Calculate an end time given a start time and second duration

# File lib/inspec_tools/inspec.rb, line 430
def end_time(start, duration)
  DateTime.parse(start) + (duration / (24*60*60))
end
find_topmost_profile_name(index, parent_name = nil) click to toggle source
# File lib/inspec_tools/inspec.rb, line 70
def find_topmost_profile_name(index, parent_name = nil)
  # Return nil when the index is out of bounds.
  # nil returned here will set the profile name to '' in the calling functions.
  return nil if index > @json['profiles'].length - 1

  # No parent profile means this is the parent
  if !@json['profiles'][index].key?('parent_profile') && (@json['profiles'][index]['name'] == parent_name || index.zero?)
    # For the initial case, parent_name will be nil, and if we are already at the parent index is also zero
    return @json['profiles'][index]['name']
  end

  parent_name = @json['profiles'][index]['parent_profile']
  find_topmost_profile_name(index + 1, parent_name)
end
generate_asset() click to toggle source
# File lib/inspec_tools/inspec.rb, line 645
def generate_asset
  asset = HappyMapperTools::StigChecklist::Asset.new
  asset.role = @metadata['role'].nil? ? 'Workstation' : @metadata['role']
  asset.type = @metadata['type'].nil? ? 'Computing' : @metadata['type']
  asset.host_name = generate_hostname
  asset.host_ip = generate_ip
  asset.host_mac = generate_mac
  asset.host_fqdn = generate_fqdn
  asset.tech_area = @metadata['tech_area'].nil? ? '' : @metadata['tech_area']
  asset.target_key = @metadata['target_key'].nil? ? '' : @metadata['target_key']
  asset.web_or_database = @metadata['web_or_database'].nil? ? '0' : @metadata['web_or_database']
  asset.web_db_site = @metadata['web_db_site'].nil? ? '' : @metadata['web_db_site']
  asset.web_db_instance = @metadata['web_db_instance'].nil? ? '' : @metadata['web_db_instance']
  asset
end
generate_ckl() click to toggle source
# File lib/inspec_tools/inspec.rb, line 606
def generate_ckl
  vuln_list = []
  @data.keys.each do |control_id|
    vuln_list.push(generate_vuln_data(@data[control_id]))
  end

  si_data_data = @metadata['stigid'] || topmost_profile_name || ''
  si_data_stigid = HappyMapperTools::StigChecklist::SiData.new('stigid', si_data_data)
  si_data_title = HappyMapperTools::StigChecklist::SiData.new('title', si_data_data)

  stig_info = HappyMapperTools::StigChecklist::StigInfo.new([si_data_stigid, si_data_title])

  istig = HappyMapperTools::StigChecklist::IStig.new(stig_info, vuln_list)
  @checklist.stig = HappyMapperTools::StigChecklist::Stigs.new(istig)

  @checklist.asset = generate_asset
end
generate_fqdn() click to toggle source
# File lib/inspec_tools/inspec.rb, line 684
def generate_fqdn
  fqdn = @metadata['fqdn']
  if fqdn.nil? && @platform.nil?
    fqdn = ''
  elsif fqdn.nil?
    fqdn = @platform[:fqdn]
  end
  fqdn
end
generate_hostname() click to toggle source
# File lib/inspec_tools/inspec.rb, line 661
def generate_hostname
  hostname = @metadata['hostname']
  if hostname.nil? && @platform.nil?
    hostname = ''
  elsif hostname.nil?
    hostname = @platform[:hostname]
  end
  hostname
end
generate_ip() click to toggle source
# File lib/inspec_tools/inspec.rb, line 694
def generate_ip
  ip = @metadata['ip']
  if ip.nil?
    nics = @platform.nil? ? [] : @platform[:network]
    nics_ips = []
    nics.each do |nic|
      nics_ips.push(*nic[:ip])
    end
    ip = nics_ips.join(',')
  end
  ip
end
generate_mac() click to toggle source
# File lib/inspec_tools/inspec.rb, line 671
def generate_mac
  mac = @metadata['mac']
  if mac.nil?
    nics = @platform.nil? ? [] : @platform[:network]
    nics_macs = []
    nics.each do |nic|
      nics_macs.push(nic[:mac])
    end
    mac = nics_macs.join(',')
  end
  mac
end
generate_title(title, json, date) click to toggle source
# File lib/inspec_tools/inspec.rb, line 707
def generate_title(title, json, date)
  title ||= "Untitled - Checklist Created from Automated InSpec Results JSON; Profiles: #{json['profiles'].map { |x| x['name'] }.join(' | ')}"
  title + " Checklist Date: #{date || Date.today.to_s}"
end
generate_vuln_data(control) click to toggle source
# File lib/inspec_tools/inspec.rb, line 624
def generate_vuln_data(control)
  vuln = HappyMapperTools::StigChecklist::Vuln.new
  stig_data_list = []

  %w{Vuln_Num Group_Title Rule_ID Rule_Ver Rule_Title Vuln_Discuss Check_Content Fix_Text}.each do |attribute|
    stig_data_list << create_stig_data_element(attribute, control)
  end
  stig_data_list << handle_severity(control)
  stig_data_list += handle_cci_ref(control)
  stig_data_list << handle_stigref

  vuln.stig_data = stig_data_list.reject(&:nil?)
  vuln.status = Utils::InspecUtil.control_status(control)
  vuln.comments = "\nAutomated compliance tests brought to you by the MITRE corporation and the InSpec project.\n\nInspec Profile: #{control[:profile_name]}\nProfile shasum: #{control[:profile_shasum]}"
  vuln.finding_details = Utils::InspecUtil.control_finding_details(control, vuln.status)
  vuln.severity_override = ''
  vuln.severity_justification = ''

  vuln
end
get_all_controls_from_json(json) click to toggle source
# File lib/inspec_tools/inspec.rb, line 583
def get_all_controls_from_json(json)
  json['profiles']&.each do |profile|
    profile['controls'].each do |control|
      @data['controls'] << control
    end
  end

  return unless json['profiles'].nil?

  json['controls'].each do |control|
    @data['controls'] << control
  end
end
handle_cci_ref(control) click to toggle source
# File lib/inspec_tools/inspec.rb, line 725
def handle_cci_ref(control)
  return [] if control[:cci_ref].nil?

  cci_data = []
  if control[:cci_ref].respond_to?(:each)
    control[:cci_ref].each do |cci_number|
      cci_data << HappyMapperTools::StigChecklist::StigData.new('CCI_REF', cci_number)
    end
    cci_data
  else
    cci_data << HappyMapperTools::StigChecklist::StigData.new('CCI_REF', control[:cci_ref])
  end
end
handle_severity(control) click to toggle source
# File lib/inspec_tools/inspec.rb, line 716
def handle_severity(control)
  return if control[:impact].nil?

  value = Utils::InspecUtil.get_impact_string(control[:impact], use_cvss_terms: false)
  return if value == 'none'

  HappyMapperTools::StigChecklist::StigData.new('Severity', value)
end
handle_stigref() click to toggle source
# File lib/inspec_tools/inspec.rb, line 739
def handle_stigref
  HappyMapperTools::StigChecklist::StigData.new('STIGRef', @title)
end
inspec_json_to_array(inspec_json) click to toggle source
This method converts an inspec json to an array of arrays

@param inspec_json : an inspec profile formatted as a json object

# File lib/inspec_tools/inspec.rb, line 562
def inspec_json_to_array(inspec_json)
  data = []
  headers = {}
  inspec_json['controls'].each do |control|
    control.each do |key, _|
      control['tags'].each { |tag, _| headers[tag] = 0 } if key == 'tags'
      control['results'].each { |result| result.each { |result_key, _| headers[result_key] = 0 } } if key == 'results'
      headers[key] = 0 unless %w{tags results}.include?(key)
    end
  end
  data.push(headers.keys)
  inspec_json['controls'].each do |json_control|
    control = []
    headers.each do |key, _|
      control.push(json_control[key] || json_control['tags'][key] || json_control['results']&.collect { |result| result[key] }&.join(",\n") || nil)
    end
    data.push(control)
  end
  data
end
parse_data_for_xccdf(json) click to toggle source

Convert raw Inspec result json into format acceptable for XCCDF transformation.

# File lib/inspec_tools/inspec.rb, line 460
def parse_data_for_xccdf(json) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
  data = {}

  controls = []
  if json['profiles'].nil?
    controls = json['controls']
  elsif json['profiles'].length == 1
    controls = json['profiles'].last['controls']
  else
    json['profiles'].each do |profile|
      controls.concat(profile['controls'])
    end
  end
  c_data = {}

  controls.each do |control|
    c_id = control['id'].to_sym
    c_data[c_id] = {}
    c_data[c_id]['id']             = control['id']    || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['title']          = control['title'] if control['title'] # Optional attribute
    c_data[c_id]['desc']           = control['desc'] || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['severity']       = control['tags']['severity'] || 'unknown'
    c_data[c_id]['gid']            = control['tags']['gid'] || control['id']
    c_data[c_id]['gtitle']         = control['tags']['gtitle'] if control['tags']['gtitle'] # Optional attribute
    c_data[c_id]['gdescription']   = control['tags']['gdescription'] if control['tags']['gdescription'] # Optional attribute
    c_data[c_id]['rid']            = control['tags']['rid'] || "r_#{c_data[c_id]['gid']}"
    c_data[c_id]['rversion']       = control['tags']['rversion'] if control['tags']['rversion'] # Optional attribute
    c_data[c_id]['rweight']        = control['tags']['rweight'] if control['tags']['rweight'] # Optional attribute where N/A is not schema compliant
    c_data[c_id]['stig_id']        = control['tags']['stig_id'] || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['cci']            = control['tags']['cci'] if control['tags']['cci'] # Optional attribute
    c_data[c_id]['legacy']         = control['tags']['legacy'] if control['tags']['legacy'] # Optional attribute
    c_data[c_id]['nist']           = control['tags']['nist'] || ['unmapped']

    # new (post-2020) inspec output places check, fix, and rationale fields in a descriptions block
    if control['descriptions'].is_a?(Hash) && control['descriptions'].key?('check') && control['descriptions'].key?('fix') && control['descriptions'].key?('rationale')
      c_data[c_id]['check']          = control['descriptions']['check'] || DATA_NOT_FOUND_MESSAGE
      c_data[c_id]['fix']            = control['descriptions']['fix'] || DATA_NOT_FOUND_MESSAGE
      c_data[c_id]['rationale']      = control['descriptions']['rationale'] || DATA_NOT_FOUND_MESSAGE
    else
      c_data[c_id]['check']          = control['tags']['check'] || DATA_NOT_FOUND_MESSAGE
      c_data[c_id]['fix']            = control['tags']['fix'] || DATA_NOT_FOUND_MESSAGE
      c_data[c_id]['rationale']      = control['tags']['rationale'] || DATA_NOT_FOUND_MESSAGE
    end
    c_data[c_id]['checkref']       = control['tags']['checkref'] || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['fix_id']         = control['tags']['fix_id'] if control['tags']['fix_id'] # Optional attribute where N/A is not schema compliant
    c_data[c_id]['cis_family']     = control['tags']['cis_family'] || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['cis_rid']        = control['tags']['cis_rid'] || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['cis_level']      = control['tags']['cis_level'] || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['impact']         = control['impact'].to_s || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['code']           = control['code'].to_s || DATA_NOT_FOUND_MESSAGE
    c_data[c_id]['results']        = parse_results_for_xccdf(control['results']) if control['results']
  end

  data['controls'] = c_data.values
  data['profiles'] = parse_profiles_for_xccdf(json['profiles'])
  data['status'] = 'success'
  # If generator exists this is a more up-to-date inspec.json so look for version in the new location, else old location
  data['inspec_version'] = json['generator'].nil? ? json['version'] : json['generator']['version']
  data
end
parse_profiles_for_xccdf(profiles) click to toggle source

Convert profile information for result processing @param profiles [Array] - The profiles section of the JSON output

# File lib/inspec_tools/inspec.rb, line 530
def parse_profiles_for_xccdf(profiles)
  return [] unless profiles

  profiles.map do |profile|
    data = {}
    data['name'] = profile['name']
    data['version'] = profile['version']
    data
  end
end
parse_results_for_xccdf(results) click to toggle source

Convert the test result data to a parseable Hash for downstream processing @param results [Array] - The results section of the JSON output

# File lib/inspec_tools/inspec.rb, line 543
def parse_results_for_xccdf(results)
  results.map do |result|
    data = {}
    data['status'] = result['status']
    data['code_desc'] = result['code_desc']
    data['run_time'] = result['run_time']
    data['start_time'] = result['start_time']
    data['resource'] = result['resource']
    data['message'] = result['message']
    data['skip_message'] = result['skip_message']
    data
  end
end
populate_identity(test_result, metadata) click to toggle source

Add information about the the account and organization executing the tests.

# File lib/inspec_tools/inspec.rb, line 254
def populate_identity(test_result, metadata)
  if metadata['identity']
    test_result.identity = HappyMapperTools::Benchmark::IdentityType.new
    test_result.identity.authenticated = true
    test_result.identity.identity = metadata['identity']['identity']
    test_result.identity.privileged = metadata['identity']['privileged']
  end

  test_result.organization = metadata['organization'] if metadata['organization']
  test_result
end
populate_remark(result, data) click to toggle source

Create a remark with contextual information about the Inspec version and profiles used @param result [HappyMapperTools::Benchmark::TestResult]

# File lib/inspec_tools/inspec.rb, line 214
def populate_remark(result, data)
  result.remark = "Results created using Inspec version #{data['inspec_version']}."
  result.remark += "\n#{data['profiles'].map { |p| "Profile: #{p['name']} Version: #{p['version']}" }.join("\n")}" if data['profiles']
  result
end
populate_results(test_result, data) click to toggle source

Build out the TestResult given all the control and result data.

# File lib/inspec_tools/inspec.rb, line 267
def populate_results(test_result, data)
  # NOTE: id is not an XCCDF 1.2 compliant identifier and will need to be updated when that support is added.
  test_result.id = 'result_1'
  test_result.starttime = run_start_time(data)
  test_result.endtime = run_end_time(data)

  # Build out individual results
  all_rule_result = []

  data['controls'].each do |control|
    next if control['results'].nil? || control['results'].empty?

    control_results =
      control['results'].map do |result|
        populate_rule_result(control, result, xccdf_status(result['status'], control['impact']))
      end

    # Consolidate results into single rule result do to lack of multiple=true attribute on Rule.
    # 1. Select the unified result status
    selected_status = control_results.reduce(control_results.first.result) { |f_status, rule_result| xccdf_and_result(f_status, rule_result.result) }

    # 2. Only choose results with that status
    # 3. Combine those results
    all_rule_result << combine_results(control_results.select { |r| r.result == selected_status })
  end

  test_result.rule_result = all_rule_result
  test_result
end
populate_rule_result(control, result, result_status) click to toggle source

Create rule-result from the control and Inspec result information

# File lib/inspec_tools/inspec.rb, line 320
def populate_rule_result(control, result, result_status)
  rule_result = HappyMapperTools::Benchmark::RuleResultType.new

  rule_result.idref = control['rid']
  rule_result.severity = control['severity']
  rule_result.time = end_time(result['start_time'], result['run_time'])
  rule_result.weight = control['rweight']

  rule_result.result = result_status
  rule_result.message = result_message(result, result_status) if result_message(result, result_status)
  rule_result.instance = result['code_desc']

  rule_result.ident = build_rule_idents(control['cci']) if control['cci']
  rule_result.ident += build_rule_idents(control['legacy']) if control['legacy']

  # Fix information is only necessary when there are failed tests
  rule_result.fix = build_rule_fix(control['fix_id']) if control['fix_id'] && result_status == 'fail'

  rule_result.check = HappyMapperTools::Benchmark::Check.new
  rule_result.check.system = control['checkref']
  rule_result.check.content = result['code_desc']
  rule_result
end
populate_score(test_result, groups) click to toggle source

Set scores for all 4 required/recommended scoring systems.

# File lib/inspec_tools/inspec.rb, line 522
def populate_score(test_result, groups)
  score = Utils::XCCDFScore.new(groups, test_result.rule_result)
  test_result.score = [score.default_score, score.flat_score, score.flat_unweighted_score, score.absolute_score]
  test_result
end
populate_target_facts(result, metadata) click to toggle source

Create all target specific information. @param result [HappyMapperTools::Benchmark::TestResult] @param metadata [Hash]

# File lib/inspec_tools/inspec.rb, line 223
def populate_target_facts(result, metadata)
  result.target = metadata['fqdn']
  result.target_address = metadata['ip'] if metadata['ip']

  all_facts = []

  if metadata['mac']
    fact = HappyMapperTools::Benchmark::Fact.new
    fact.name = 'urn:xccdf:fact:asset:identifier:mac'
    fact.type = 'string'
    fact.fact = metadata['mac']
    all_facts << fact
  end

  if metadata['ip']
    fact = HappyMapperTools::Benchmark::Fact.new
    fact.name = 'urn:xccdf:fact:asset:identifier:ipv4'
    fact.type = 'string'
    fact.fact = metadata['ip']
    all_facts << fact
  end

  return result unless all_facts.size.nonzero?

  facts = HappyMapperTools::Benchmark::TargetFact.new
  facts.fact = all_facts
  result.target_facts = facts
  result
end
result_message(result, xccdf_status) click to toggle source

Builds the message information for rule results @param result [Hash] A single Inspec result @param xccdf_status [String] the xccdf calculated result status for the provided result

# File lib/inspec_tools/inspec.rb, line 437
def result_message(result, xccdf_status)
  return unless result['message'] || result['skip_message']

  message = HappyMapperTools::Benchmark::MessageType.new
  # Including the code of the check and the resulting message if there is one.
  message.message = "#{result['code_desc'] ? "#{result['code_desc']}\n\n" : ''}#{result['message'] || result['skip_message']}"
  message.severity = result_message_severity(xccdf_status)
  message
end
result_message_severity(xccdf_status) click to toggle source

All rule-result messages require a defined severity. This determines a value to use based upon the result XCCDF status.

# File lib/inspec_tools/inspec.rb, line 448
def result_message_severity(xccdf_status)
  case xccdf_status
  when 'fail'
    'error'
  when 'notapplicable'
    'warning'
  else
    'info'
  end
end
run_end_time(data) click to toggle source

Return the latest time of execution accounting for Inspec duration.

# File lib/inspec_tools/inspec.rb, line 309
def run_end_time(data)
  end_times =
    data['controls'].map do |control|
      next if control['results'].nil?

      control['results'].map { |result| end_time(result['start_time'], result['run_time']) }
    end
  end_times.flatten.max
end
run_start_time(data) click to toggle source

Return the earliest time of execution.

# File lib/inspec_tools/inspec.rb, line 298
def run_start_time(data)
  start_times =
    data['controls'].map do |control|
      next if control['results'].nil?

      control['results'].map { |result| DateTime.parse(result['start_time']) }
    end
  start_times.flatten.min
end
to_xml(metadata, attributes, data) click to toggle source

Build entire XML document and produce final output @param metadata [Hash] Data representing a system under scan

# File lib/inspec_tools/inspec.rb, line 87
def to_xml(metadata, attributes, data)
  attributes = {} if attributes.nil?
  build_benchmark_header(attributes)
  build_groups(attributes, data)
  # Only populate results if a target is defined so that conformant XML is produced.
  @benchmark.testresult = build_test_results(metadata, data) if metadata['fqdn']
  @benchmark.to_xml
end
topmost_profile_name() click to toggle source
# File lib/inspec_tools/inspec.rb, line 66
def topmost_profile_name
  find_topmost_profile_name(0)
end
update_ckl() click to toggle source
# File lib/inspec_tools/inspec.rb, line 597
def update_ckl
  @checklist = HappyMapperTools::StigChecklist::Checklist.parse(@cklist.to_s)
  @data.keys.each do |control_id|
    vuln = @checklist.where('Vuln_Num', control_id.to_s)
    vuln.status = Utils::InspecUtil.control_status(@data[control_id])
    vuln.finding_details << Utils::InspecUtil.control_finding_details(@data[control_id], vuln.status)
  end
end
xccdf_and_result(one, two) click to toggle source

When more than one result occurs for a rule and the specification does not declare multiple, the result must be combined. This determines the appropriate result to be selected when there are two to compare. @param one [String] A rule-result status @param two [String] A rule-result status @return The result of the AND operation.

# File lib/inspec_tools/inspec.rb, line 374
def xccdf_and_result(one, two)
  # From XCCDF specification truth table
  # P = pass
  # F = fail
  # U = unknown
  # E = error
  # N = notapplicable
  # K = notchecked
  # S = notselected
  # I = informational

  case one
  when 'pass'
    %w{fail unknown}.any? { |s| s == two } ? two : one
  when 'fail'
    one
  when 'unknown'
    two == 'fail' ? two : one
  when 'notapplicable'
    %w{pass fail unknown}.any? { |s| s == two } ? two : one
  when 'notchecked'
    %w{pass fail unknown notapplicable}.any? { |s| s == two } ? two : one
  end
end
xccdf_status(inspec_status, impact) click to toggle source

Map the Inspec result status to appropriate XCCDF test result status. XCCDF options include: pass, fail, error, unknown, notapplicable, notchecked, notselected, informational, fixed

@param inspec_status [String] The reported Inspec status from an individual test @param impact [String] A value of 0.0 - 1.0 @return A valid Inspec status.

# File lib/inspec_tools/inspec.rb, line 350
def xccdf_status(inspec_status, impact)
  # Currently, there is no good way to map an Inspec result status to one of XCCDF status unknown or notselected.
  case inspec_status
  when 'failed'
    'fail'
  when 'passed'
    'pass'
  when 'skipped'
    if impact.to_f.zero?
      'notapplicable'
    else
      'notchecked'
    end
  else
    # In the event Inspec adds a new unaccounted for status, mapping to XCCDF unknown.
    'unknown'
  end
end