Class: Simp::BeakerHelpers::SSG

Inherits:
Object
  • Object
show all
Defined in:
lib/simp/beaker_helpers/ssg.rb

Overview

Helpers for working with the SCAP Security Guide

Constant Summary collapse

GIT_REPO =
'https://github.com/ComplianceAsCode/content.git'
GIT_BRANCH =

If this is not set, the closest tag to the default branch will be used

ENV['BEAKER_ssg_branch']
EL_PACKAGES =
[
  'PyYAML',
  'cmake',
  'git',
  'openscap-python',
  'openscap-utils',
  'python-lxml',
  'python-jinja2'
]
OS_INFO =
{
  'RedHat' => {
    '6' => {
      'required_packages' => EL_PACKAGES,
      'ssg' => {
        'profile_target' => 'rhel6',
        'build_target'   => 'rhel6',
        'datastream'     => 'ssg-rhel6-ds.xml'
      }
    },
    '7' => {
      'required_packages' => EL_PACKAGES,
      'ssg' => {
        'profile_target' => 'rhel7',
        'build_target'   => 'rhel7',
        'datastream'     => 'ssg-rhel7-ds.xml'
      }
    }
  },
  'CentOS' => {
    '6' => {
      'required_packages' => EL_PACKAGES,
      'ssg' => {
        'profile_target' => 'rhel6',
        'build_target'   => 'centos6',
        'datastream'     => 'ssg-centos6-ds.xml'
      }
    },
    '7' => {
      'required_packages' => EL_PACKAGES,
      'ssg' => {
        'profile_target' => 'rhel7',
        'build_target'   => 'centos7',
        'datastream'     => 'ssg-centos7-ds.xml'
      }
    }
  },
  'OracleLinux' => {
    '7' => {
      'required_packages' => EL_PACKAGES,
      'ssg' => {
        'profile_target' => 'ol7',
        'build_target'   => 'ol7',
        'datastream'     => 'ssg-ol7-ds.xml'
      }
    }
  }
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sut) ⇒ SSG

Create a new SSG helper for the specified host

Parameters:

  • sut

    The SUT against which to run



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/simp/beaker_helpers/ssg.rb', line 88

def initialize(sut)
  @sut = sut

  @os = fact_on(@sut, 'operatingsystem')
  @os_rel = fact_on(@sut, 'operatingsystemmajrelease')

  sut.mkdir_p('scap_working_dir')

  @scap_working_dir = on(sut, 'cd scap_working_dir && pwd').stdout.strip

  unless OS_INFO[@os]
    fail("Error: The '#{@os}' Operating System is not supported")
  end

  OS_INFO[@os][@os_rel]['required_packages'].each do |pkg|
    @sut.install_package(pkg)
  end

  @output_dir = File.absolute_path('sec_results/ssg')

  unless File.directory?(@output_dir)
    FileUtils.mkdir_p(@output_dir)
  end

  @result_file = "#{@sut.hostname}-ssg-#{Time.now.to_i}"


  get_ssg_datastream
end

Instance Attribute Details

#scap_working_dirObject

Returns the value of attribute scap_working_dir.



81
82
83
# File 'lib/simp/beaker_helpers/ssg.rb', line 81

def scap_working_dir
  @scap_working_dir
end

Class Method Details

.process_ssg_results(result_file, filter = nil) ⇒ Hash

Process the results of an SSG run

Returns:

  • (Hash)

    A Hash of statistics and a formatted report



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/simp/beaker_helpers/ssg.rb', line 175

def self.process_ssg_results(result_file, filter=nil)
  require 'highline'
  require 'nokogiri'

  HighLine.colorize_strings

  fail("Could not find results XML file '#{result_file}'") unless File.exist?(result_file)

  puts "Processing #{result_file}"
  doc = Nokogiri::XML(File.open(result_file))

  # because I'm lazy
  doc.remove_namespaces!

  if filter
    # XPATH to get the pertinent test results:
    #   Any node named 'rule-result' for which the attribute 'idref'
    #   contains filter
    result_nodes = doc.xpath("//rule-result[contains(@idref,'#{filter}')]")
  else
    result_nodes = doc.xpath('//rule-result')
  end

  stats = {
    :failed  => [],
    :passed  => [],
    :skipped => [],
    :filter  => filter.nil? ? 'No Filter' : filter,
    :report  => nil,
    :score   => 0
  }

  result_nodes.each do |rule_result|
    # Results are recorded in a child node named 'result'.
    # Within the 'result' node, the actual result string is
    # the content of that node's (only) child node.

    result = rule_result.element_children.at('result')
    result_id = rule_result.attributes['idref'].value.to_s
    result_value = [
      'Title: ' + doc.xpath("//Rule[@id='#{result_id}']/title/text()").first.to_s,
      '  ID: ' + result_id
    ].join("\n")

    if result.child.content == 'fail'
      stats[:failed] << result_value.red
    elsif result.child.content == 'pass'
      stats[:passed] << result_value.green
    else
      stats[:skipped] << result_value.yellow
    end
  end

  report = []

  report << '== Skipped =='
  report << stats[:skipped].join("\n")

  report << '== Passed =='
  report << stats[:passed].join("\n")

  report << '== Failed =='
  report << stats[:failed].join("\n")


  report << 'OSCAP Statistics:'

  if filter
    report << "  * Used Filter: 'idref' ~= '#{stats[:filter]}'"
  end

  report << "  * Passed: #{stats[:passed].count.to_s.green}"
  report << "  * Failed: #{stats[:failed].count.to_s.red}"
  report << "  * Skipped: #{stats[:skipped].count.to_s.yellow}"

  score = 0

  if (stats[:passed].count + stats[:failed].count) > 0
    score = ((stats[:passed].count.to_f/(stats[:passed].count + stats[:failed].count)) * 100.0).round(0)
  end

  report << "\n Score: #{score}%"

  stats[:score]  = score
  stats[:report] = report.join("\n")

  return stats
end

Instance Method Details

#evaluate(profile, remediate = false) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/simp/beaker_helpers/ssg.rb', line 126

def evaluate(profile, remediate=false)
  cmd = "cd #{@scap_working_dir}; oscap xccdf eval"

  if remediate
    cmd += ' --remediate'
  end

  cmd += %( --fetch-remote-resources --profile #{profile} --results #{@result_file}.xml --report #{@result_file}.html #{OS_INFO[@os][@os_rel]['ssg']['datastream']})

  # We accept all exit codes here because there have occasionally been
  # failures in the SSG content and we're not testing that.

  on(@sut, cmd, :accept_all_exit_codes => true)

  ['xml', 'html'].each do |ext|
    path = "#{@scap_working_dir}/#{@result_file}.#{ext}"
    scp_from(@sut, path, @output_dir)

    fail("Could not retrieve #{path} from #{@sut}") unless File.exist?(File.join(@output_dir, "#{@result_file}.#{ext}"))
  end
end

#process_ssg_results(filter = nil) ⇒ Object

Retrieve a subset of test results based on a match to filter

FIXME:

  • This is a hack! Should be searching for rules based on a set set of STIG ids, but don’t see those ids in the oscap results xml. Further mapping is required…

  • Create the same report structure as inspec



167
168
169
# File 'lib/simp/beaker_helpers/ssg.rb', line 167

def process_ssg_results(filter=nil)
  self.class.process_ssg_results(File.join(@output_dir, @result_file) + '.xml', filter)
end

#profile_targetObject



118
119
120
# File 'lib/simp/beaker_helpers/ssg.rb', line 118

def profile_target
  OS_INFO[@os][@os_rel]['ssg']['profile_target']
end

#remediate(profile) ⇒ Object



122
123
124
# File 'lib/simp/beaker_helpers/ssg.rb', line 122

def remediate(profile)
  evaluate(profile, true)
end

#write_report(report) ⇒ Object

Output the report

Parameters:

  • report

    The results Hash



153
154
155
156
157
# File 'lib/simp/beaker_helpers/ssg.rb', line 153

def write_report(report)
  File.open(File.join(@output_dir, @result_file) + '.report', 'w') do |fh|
    fh.puts(report[:report].uncolor)
  end
end