smart_proxy_openscap 0.7.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/bin/smart-proxy-openscap-send +5 -1
  4. data/lib/smart_proxy_openscap/arf_parser.rb +74 -17
  5. data/lib/smart_proxy_openscap/content_parser.rb +19 -25
  6. data/lib/smart_proxy_openscap/fetch_scap_file.rb +45 -0
  7. data/lib/smart_proxy_openscap/foreman_arf_forwarder.rb +15 -0
  8. data/lib/smart_proxy_openscap/foreman_forwarder.rb +19 -16
  9. data/lib/smart_proxy_openscap/foreman_oval_forwarder.rb +19 -0
  10. data/lib/smart_proxy_openscap/openscap_api.rb +59 -28
  11. data/lib/smart_proxy_openscap/openscap_exception.rb +1 -0
  12. data/lib/smart_proxy_openscap/openscap_html_generator.rb +1 -1
  13. data/lib/smart_proxy_openscap/openscap_import_api.rb +3 -3
  14. data/lib/smart_proxy_openscap/openscap_lib.rb +5 -3
  15. data/lib/smart_proxy_openscap/openscap_plugin.rb +2 -1
  16. data/lib/smart_proxy_openscap/oval_report_parser.rb +54 -0
  17. data/lib/smart_proxy_openscap/oval_report_storage_fs.rb +26 -0
  18. data/lib/smart_proxy_openscap/profiles_parser.rb +22 -23
  19. data/lib/smart_proxy_openscap/spool_forwarder.rb +4 -4
  20. data/lib/smart_proxy_openscap/storage.rb +0 -2
  21. data/lib/smart_proxy_openscap/storage_fs.rb +7 -4
  22. data/lib/smart_proxy_openscap/storage_fs_common.rb +42 -0
  23. data/lib/smart_proxy_openscap/version.rb +1 -1
  24. data/settings.d/openscap.yml.example +3 -0
  25. data/smart_proxy_openscap.gemspec +2 -0
  26. data/test/data/oval-results.xml.bz2 +0 -0
  27. data/test/data/rhel-7-including-unpatched.oval.xml.bz2 +0 -0
  28. data/test/fetch_oval_content_api_test.rb +38 -0
  29. data/test/fetch_scap_api_test.rb +1 -1
  30. data/test/oval_report_parser_test.rb +14 -0
  31. data/test/post_oval_report_api_test.rb +30 -0
  32. data/test/post_report_api_test.rb +2 -2
  33. data/test/scap_content_parser_api_test.rb +1 -1
  34. data/test/script_class_test.rb +0 -58
  35. metadata +29 -11
  36. data/bin/smart-proxy-arf-json +0 -7
  37. data/bin/smart-proxy-scap-profiles +0 -7
  38. data/bin/smart-proxy-scap-validation +0 -7
  39. data/lib/smart_proxy_openscap/arf_json.rb +0 -114
  40. data/lib/smart_proxy_openscap/fetch_scap_content.rb +0 -17
  41. data/lib/smart_proxy_openscap/fetch_tailoring_file.rb +0 -17
  42. data/lib/smart_proxy_openscap/scap_profiles.rb +0 -52
  43. data/lib/smart_proxy_openscap/scap_validation.rb +0 -35
@@ -6,4 +6,5 @@ module Proxy::OpenSCAP
6
6
  class FileNotFound < StandardError; end
7
7
  class StoreCorruptedError < StandardError; end
8
8
  class ReportUploadError < StandardError; end
9
+ class ReportDecompressError < StandardError; end
9
10
  end
@@ -30,7 +30,7 @@ module Proxy
30
30
 
31
31
  def file_path_in_storage
32
32
  path_to_dir = Proxy::OpenSCAP::Plugin.settings.reportsdir
33
- storage = Proxy::OpenSCAP::StorageFS.new(path_to_dir, @cname, @id, @date)
33
+ storage = Proxy::OpenSCAP::StorageFs.new(path_to_dir, @cname, @id, @date)
34
34
  storage.get_path(@digest)
35
35
  end
36
36
  end
@@ -14,15 +14,15 @@ module Proxy::OpenSCAP
14
14
 
15
15
  post_to_foreman = ForemanForwarder.new.post_arf_report(cn, policy, date, request.body.string, Proxy::OpenSCAP::Plugin.settings.timeout)
16
16
  begin
17
- Proxy::OpenSCAP::StorageFS.new(Proxy::OpenSCAP::Plugin.settings.reportsdir, cn, post_to_foreman['id'], date).store_archive(request.body.string)
17
+ Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.reportsdir, cn, post_to_foreman['id'], date).store_archive(request.body.string)
18
18
  rescue Proxy::OpenSCAP::StoreReportError => e
19
- Proxy::OpenSCAP::StorageFS.new(Proxy::OpenSCAP::Plugin.settings.failed_dir, cn, post_to_foreman['id'], date).store_failed(request.body.string)
19
+ Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.failed_dir, cn, post_to_foreman['id'], date).store_failed(request.body.string)
20
20
  logger.error "Failed to save Report in reports directory (#{Proxy::OpenSCAP::Plugin.settings.reportsdir}). Failed with: #{e.message}.
21
21
  Saving file in #{Proxy::OpenSCAP::Plugin.settings.failed_dir}. Please copy manually to #{Proxy::OpenSCAP::Plugin.settings.reportsdir}"
22
22
  rescue *HTTP_ERRORS => e
23
23
  ### If the upload to foreman fails then store it in the spooldir
24
24
  logger.error "Failed to upload to Foreman, saving in spool. Failed with: #{e.message}"
25
- Proxy::OpenSCAP::StorageFS.new(Proxy::OpenSCAP::Plugin.settings.spooldir, cn, policy, date).store_spool(request.body.string)
25
+ Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.spooldir, cn, policy, date).store_spool(request.body.string)
26
26
  rescue Proxy::OpenSCAP::StoreSpoolError => e
27
27
  log_halt 500, e.message
28
28
  end
@@ -16,16 +16,18 @@ require 'proxy/error'
16
16
  require 'yaml'
17
17
  require 'ostruct'
18
18
  require 'proxy/request'
19
- require 'smart_proxy_openscap/fetch_scap_content'
20
- require 'smart_proxy_openscap/foreman_forwarder'
19
+ require 'smart_proxy_openscap/foreman_arf_forwarder'
20
+ require 'smart_proxy_openscap/foreman_oval_forwarder'
21
21
  require 'smart_proxy_openscap/content_parser'
22
22
  require 'smart_proxy_openscap/openscap_exception'
23
23
  require 'smart_proxy_openscap/arf_parser'
24
24
  require 'smart_proxy_openscap/spool_forwarder'
25
25
  require 'smart_proxy_openscap/openscap_html_generator'
26
- require 'smart_proxy_openscap/fetch_tailoring_file'
27
26
  require 'smart_proxy_openscap/policy_parser'
28
27
  require 'smart_proxy_openscap/profiles_parser'
28
+ require 'smart_proxy_openscap/oval_report_storage_fs'
29
+ require 'smart_proxy_openscap/oval_report_parser'
30
+ require 'smart_proxy_openscap/fetch_scap_file'
29
31
 
30
32
  module Proxy::OpenSCAP
31
33
  extend ::Proxy::Log
@@ -22,6 +22,7 @@ module Proxy::OpenSCAP
22
22
  :contentdir => File.join(APP_ROOT, 'openscap/content'),
23
23
  :reportsdir => File.join(APP_ROOT, 'openscap/reports'),
24
24
  :failed_dir => File.join(APP_ROOT, 'openscap/failed'),
25
- :tailoring_dir => File.join(APP_ROOT, 'openscap/tailoring')
25
+ :tailoring_dir => File.join(APP_ROOT, 'openscap/tailoring'),
26
+ :oval_content_dir => File.join(APP_ROOT, 'openscap/oval_content')
26
27
  end
27
28
  end
@@ -0,0 +1,54 @@
1
+ require 'smart_proxy_openscap/openscap_exception'
2
+ require 'openscap_parser/oval_report'
3
+
4
+ module Proxy::OpenSCAP
5
+ class OvalReportParser
6
+ include Proxy::Log
7
+
8
+ def parse_cves(report_data)
9
+ report = oval_report report_data
10
+ results = report.definition_results.reduce({}) do |memo, result|
11
+ memo.tap { |acc| acc[result.definition_id] = parse_cve_res result }
12
+ end
13
+
14
+ report.definitions.map do |definition|
15
+ results[definition.id].merge(parse_cve_def definition)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def parse_cve_def(definition)
22
+ refs = definition.references.reduce([]) do |memo, ref|
23
+ memo.tap { |acc| acc << { :ref_id => ref.ref_id, :ref_url => ref.ref_url } }
24
+ end
25
+
26
+ { :references => refs, :definition_id => definition.id }
27
+ end
28
+
29
+ def parse_cve_res(result)
30
+ { :result => result.result }
31
+ end
32
+
33
+ def oval_report(report_data)
34
+ decompressed = decompress report_data
35
+ ::OpenscapParser::OvalReport.new(decompressed)
36
+ end
37
+
38
+ def decompress(report_data)
39
+ begin
40
+ file = Tempfile.new
41
+ file.write report_data
42
+ file.rewind
43
+ decompressed = `bunzip2 -dc #{file.path}`
44
+ rescue => e
45
+ logger.error e
46
+ raise Proxy::OpenSCAP::ReportDecompressError, "Failed to decompress received report bzip, cause: #{e.message}"
47
+ ensure
48
+ file.close
49
+ file.unlink
50
+ end
51
+ decompressed
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,26 @@
1
+ require 'smart_proxy_openscap/storage_fs_common'
2
+ require 'smart_proxy_openscap/openscap_exception'
3
+
4
+ module Proxy::OpenSCAP
5
+ class OvalReportStorageFs
6
+ include StorageFsCommon
7
+
8
+ def initialize(path_to_dir, oval_policy_id, cname, reported_at)
9
+ @namespace = 'oval'
10
+ @reported_at = reported_at
11
+ @path = "#{path_to_dir}/#{@namespace}/#{oval_policy_id}/#{cname}/"
12
+ end
13
+
14
+ def store_report(report_data)
15
+ store(report_data, StoreReportError)
16
+ end
17
+
18
+ private
19
+
20
+ def store_file(path_to_store, report_data)
21
+ target_path = "#{path_to_store}#{@reported_at}"
22
+ File.open(target_path, 'w') { |f| f.write(report_data) }
23
+ target_path
24
+ end
25
+ end
26
+ end
@@ -1,31 +1,30 @@
1
- require 'smart_proxy_openscap/shell_wrapper'
1
+ require 'openscap_parser/datastream_file'
2
+ require 'openscap_parser/tailoring_file'
2
3
 
3
4
  module Proxy
4
5
  module OpenSCAP
5
- class ProfilesParser < ShellWrapper
6
- def initialize(type)
7
- @type = type
8
- @script_name = 'smart-proxy-scap-profiles'
9
- end
10
-
11
- def profiles(scap_file)
12
- execute_shell_command scap_file
13
- end
14
-
15
- def out_filename
16
- "#{in_filename}json-"
17
- end
18
-
19
- def in_filename
20
- "#{super}-#{@type}-profiles-"
21
- end
6
+ class ProfilesParser
7
+ def profiles(file_type, scap_file)
8
+ profiles = []
9
+ error_msg = 'Failed to parse profiles'
10
+ begin
11
+ case file_type
12
+ when 'scap_content'
13
+ profiles = ::OpenscapParser::DatastreamFile.new(scap_file).benchmark.profiles
14
+ when 'tailoring_file'
15
+ profiles = ::OpenscapParser::TailoringFile.new(scap_file).tailoring.profiles
16
+ else
17
+ raise OpenSCAPException, "Unknown file type, expected 'scap_content' or 'tailoring_file'"
18
+ end
19
+ rescue Nokogiri::XML::SyntaxError
20
+ raise OpenSCAPException, error_msg
21
+ end
22
22
 
23
- def failure_message
24
- "Failure when running script which extracts profiles from scap file"
25
- end
23
+ raise OpenSCAPException, error_msg if profiles.empty?
26
24
 
27
- def command(in_file, out_file)
28
- "#{script_location} #{in_file.path} #{out_file.path} #{@type}"
25
+ result = profiles.reduce({}) do |memo, profile|
26
+ memo.tap { |acc| acc[profile.id] = profile.title }
27
+ end.to_json
29
28
  end
30
29
  end
31
30
  end
@@ -52,13 +52,13 @@ module Proxy::OpenSCAP
52
52
 
53
53
  def forward_arf_file(cname, policy_id, date, arf_file_path)
54
54
  data = File.open(arf_file_path, 'rb') { |io| io.read }
55
- post_to_foreman = ForemanForwarder.new.post_arf_report(cname, policy_id, date, data, @loaded_settings.timeout)
56
- Proxy::OpenSCAP::StorageFS.new(@loaded_settings.reportsdir, cname, post_to_foreman['id'], date).store_archive(data)
55
+ post_to_foreman = ForemanArfForwarder.new.post_report(cname, policy_id, date, data, @loaded_settings.timeout)
56
+ Proxy::OpenSCAP::StorageFs.new(@loaded_settings.reportsdir, cname, post_to_foreman['id'], date).store_archive(data)
57
57
  File.delete arf_file_path
58
- rescue Proxy::OpenSCAP::OpenSCAPException => e
58
+ rescue Nokogiri::XML::SyntaxError, Proxy::OpenSCAP::ReportDecompressError => e
59
59
  logger.error "Failed to parse Arf Report at #{arf_file_path}, moving to #{@loaded_settings.corrupted_dir}"
60
60
 
61
- Proxy::OpenSCAP::StorageFS.new(@loaded_settings.corrupted_dir, cname, policy_id, date).
61
+ Proxy::OpenSCAP::StorageFs.new(@loaded_settings.corrupted_dir, cname, policy_id, date).
62
62
  move_corrupted(arf_file_path.split('/').last, @loaded_settings.spooldir)
63
63
  rescue Proxy::OpenSCAP::ReportUploadError => e
64
64
  logger.error "Failed to upload Arf Report at #{arf_file_path}, cause: #{e.message}, the report will be deleted."
@@ -2,8 +2,6 @@ require 'smart_proxy_openscap/openscap_exception'
2
2
 
3
3
  module Proxy::OpenSCAP
4
4
  class Storage
5
- include ::Proxy::Log
6
-
7
5
  def initialize(path_to_dir, cname, id, date)
8
6
  @namespace = 'arf'
9
7
  @cname = cname
@@ -1,8 +1,11 @@
1
1
  require 'pathname'
2
2
  require 'smart_proxy_openscap/storage'
3
+ require 'smart_proxy_openscap/storage_fs_common'
3
4
 
4
5
  module Proxy::OpenSCAP
5
- class StorageFS < Storage
6
+ class StorageFs < Storage
7
+ include StorageFsCommon
8
+
6
9
  def store_archive(data)
7
10
  store(data, StoreReportError)
8
11
  end
@@ -57,9 +60,9 @@ module Proxy::OpenSCAP
57
60
 
58
61
  private
59
62
 
60
- def store_arf(spool_arf_dir, data)
63
+ def store_file(path_to_store, data)
61
64
  filename = Digest::SHA256.hexdigest data
62
- target_path = spool_arf_dir + filename
65
+ target_path = path_to_store + filename
63
66
  File.open(target_path,'w') { |f| f.write(data) }
64
67
  target_path
65
68
  end
@@ -91,7 +94,7 @@ module Proxy::OpenSCAP
91
94
  end
92
95
 
93
96
  begin
94
- target_path = store_arf(@path, data)
97
+ target_path = store_file(@path, data)
95
98
  rescue StandardError => e
96
99
  raise error_type, "Could not store file: #{e.message}"
97
100
  end
@@ -0,0 +1,42 @@
1
+ module Proxy::OpenSCAP
2
+ module StorageFsCommon
3
+ include ::Proxy::Log
4
+
5
+ private
6
+
7
+ def create_directory
8
+ begin
9
+ FileUtils.mkdir_p @path
10
+ rescue StandardError => e
11
+ logger.error "Could not create '#{@path}' directory: #{e.message}"
12
+ raise e
13
+ end
14
+ @path
15
+ end
16
+
17
+ def move(source, error_type)
18
+ begin
19
+ create_directory
20
+ FileUtils.mv source, @path
21
+ rescue StandardError => e
22
+ raise error_type, "Could not move file: #{e.message}"
23
+ end
24
+ end
25
+
26
+ def store(data, error_type)
27
+ begin
28
+ create_directory
29
+ rescue StandardError => e
30
+ raise error_type, "Could not fulfill request: #{e.message}"
31
+ end
32
+
33
+ begin
34
+ target_path = store_file(@path, data)
35
+ rescue StandardError => e
36
+ raise error_type, "Could not store file: #{e.message}"
37
+ end
38
+
39
+ logger.debug "File #{target_path} stored in reports dir."
40
+ end
41
+ end
42
+ end
@@ -10,6 +10,6 @@
10
10
 
11
11
  module Proxy
12
12
  module OpenSCAP
13
- VERSION = '0.7.3'
13
+ VERSION = '0.9.0'
14
14
  end
15
15
  end
@@ -31,3 +31,6 @@
31
31
  # Affects sending reports to Foreman (directly and from spool) and fetching scap content or tailoring file
32
32
  # for distribution to clients
33
33
  #:timeout: 60
34
+
35
+ # Directory where OpenSCAP OVAL content bzipped XML are stored
36
+ #:oval_content_dir: /var/lib/openscap/oval_content
@@ -14,10 +14,12 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.files = `git ls-files`.split("\n") - ['.gitignore']
16
16
  s.executables = ['smart-proxy-openscap-send']
17
+ s.requirements = 'bzip2'
17
18
 
18
19
  s.add_development_dependency('rake')
19
20
  s.add_development_dependency('rack-test')
20
21
  s.add_development_dependency('mocha')
21
22
  s.add_development_dependency('webmock')
22
23
  s.add_dependency 'openscap', '~> 0.4.7'
24
+ s.add_dependency 'openscap_parser', '~> 1.0.2'
23
25
  end
Binary file
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+ require 'smart_proxy_openscap'
3
+ require 'smart_proxy_openscap/openscap_api'
4
+
5
+ ENV['RACK_ENV'] = 'test'
6
+
7
+ class FetchOvalContentApiTest < Test::Unit::TestCase
8
+ include Rack::Test::Methods
9
+
10
+ def setup
11
+ @foreman_url = 'https://foreman.example.com'
12
+ @fixture_path = "/test/data/rhel-7-including-unpatched.oval.xml.bz2"
13
+ @fixture_full_path = File.join(Dir.getwd, @fixture_path)
14
+ Proxy::SETTINGS.stubs(:foreman_url).returns(@foreman_url)
15
+ @results_path = ("#{Dir.getwd}/test/test_run_files")
16
+ FileUtils.mkdir_p(@results_path)
17
+ Proxy::OpenSCAP::Plugin.settings.stubs(:oval_content_dir).returns(@results_path)
18
+ @oval_content = File.new(@fixture_full_path).read
19
+ @digest = Digest::SHA256.hexdigest @oval_content
20
+ @policy_id = 1
21
+ end
22
+
23
+ def teardown
24
+ FileUtils.rm_rf(Dir.glob("#{@results_path}/*"))
25
+ end
26
+
27
+ def app
28
+ ::Proxy::OpenSCAP::Api.new
29
+ end
30
+
31
+ def test_get_oval_content_from_file
32
+ FileUtils.mkdir("#{@results_path}/#{@policy_id}")
33
+ FileUtils.cp(@fixture_full_path, "#{@results_path}/#{@policy_id}/#{@digest}.oval.xml.bz2")
34
+ get "/oval_policies/#{@policy_id}/oval_content/#{@digest}"
35
+ assert_equal("application/x-bzip2", last_response.header["Content-Type"], "Response header should be application/x-bzip2")
36
+ assert(last_response.successful?, "Response should be success")
37
+ end
38
+ end
@@ -54,7 +54,7 @@ class FetchScapApiTest < Test::Unit::TestCase
54
54
  end
55
55
 
56
56
  def test_get_scap_content_permissions
57
- Proxy::OpenSCAP::FetchScapContent.any_instance.stubs(:get_policy_content).raises(Errno::EACCES)
57
+ Proxy::OpenSCAP::FetchScapFile.any_instance.stubs(:fetch).raises(Errno::EACCES)
58
58
  stub_request(:get, "#{@foreman_url}/api/v2/compliance/policies/#{@policy_id}/content").to_return(:body => @scap_content)
59
59
  get "/policies/#{@policy_id}/content/#{@digest}"
60
60
  assert_equal(500, last_response.status, "No permissions should raise error 500")
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ require 'smart_proxy_openscap'
3
+ require 'smart_proxy_openscap/oval_report_parser'
4
+
5
+ class OvalReportParserTest < Test::Unit::TestCase
6
+
7
+ def test_oval_report_parsing
8
+ oval_report = File.open("#{Dir.getwd}/test/data/oval-results.xml.bz2").read
9
+ res = Proxy::OpenSCAP::OvalReportParser.new.parse_cves oval_report
10
+ refute res.empty?
11
+ assert res.first[:result]
12
+ refute res.first[:references].empty?
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+ require 'smart_proxy_openscap'
3
+ require 'smart_proxy_openscap/openscap_api'
4
+
5
+ ENV['RACK_ENV'] = 'test'
6
+
7
+ class PostOvalReportApiTest < Test::Unit::TestCase
8
+ include Rack::Test::Methods
9
+
10
+ setup do
11
+ @foreman_url = 'https://foreman.example.com'
12
+ Proxy::SETTINGS.stubs(:foreman_url).returns(@foreman_url)
13
+ @oval_report = File.open("#{Dir.getwd}/test/data/oval-results.xml.bz2").read
14
+ @cname = 'node.example.org'
15
+ @date = Time.now.to_i
16
+ @policy_id = 1
17
+ Proxy::OpenSCAP.stubs(:common_name).returns(@cname)
18
+ end
19
+
20
+ def app
21
+ ::Proxy::OpenSCAP::Api.new
22
+ end
23
+
24
+ def test_post_oval_report_to_foreman
25
+ stub_request(:post, "#{@foreman_url}/api/v2/compliance/oval_reports/#{@cname}/#{@policy_id}/#{@date}")
26
+ .to_return(:status => 200, :body => '{ "result": "ok" }')
27
+ post "/oval_reports/#{@policy_id}", @oval_report, 'CONTENT_TYPE' => 'text/xml', 'CONTENT_ENCODING' => 'x-bzip2'
28
+ assert(last_response.successful?, "Should be a success")
29
+ end
30
+ end