openscap_parser 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04a15bff1610dd289e619dfe869fa31f0af4309acac276aae1f5c10b15197238
4
- data.tar.gz: 0706ef0b61d96b7d3dfa73ed7f6696caaa18bbeb6a4d81eb922ef8ca75b5f4f7
3
+ metadata.gz: 5bbc310b2adc402a398f0b9e8524199a91c06e0b74c2f2757a9fdae292c645c5
4
+ data.tar.gz: 1d8314602c86e916aff3b19b09d88a858892635beaee738bd79177bf38434abb
5
5
  SHA512:
6
- metadata.gz: 2b066da57a23d5da4c235b97ce59c560f2c132e2275750e3861f7951905b5b56911388380aa81ff73ffe0e01c6b69b82821fa23a36738ed085548116a748b12b
7
- data.tar.gz: 66aa26a5e7a4bf1cd89392304eb1d0cb77d4716384dd4a920684534ab4f3a5d360a0941419add841e003d724810acc08f07542ce01825a902cd2902cc5d1917d
6
+ metadata.gz: f2d316e18f0d82b9597b24b306dbae8615a044648102c085170005d1eaa9737263c11ec6a3c4e8af4e355cc59a9e57f78f560fe46f11c0e2cabfd134220153d7
7
+ data.tar.gz: 59f11993820161a4ece0d23df623ea3412cfb90d1895a5dd7a11fd1fa519e8eb3afbd0812c9a4a78ba1d254c74bcb58578f849ec773ace989ca91b07cffa4fab
data/Dockerfile ADDED
@@ -0,0 +1,15 @@
1
+ # docker build . -t openscap_parser # build the container image
2
+ # docker run -itv $PWD:/app:z openscap_parser rake # run tests
3
+ # docker run -itv $PWD:/app:z openscap_parser pry --gem # console
4
+
5
+ FROM ruby:2.5
6
+
7
+ RUN gem update bundler
8
+
9
+ WORKDIR /app
10
+
11
+ COPY . ./
12
+
13
+ RUN bundle -j4
14
+
15
+ CMD bash
data/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/openscap_parser`. To experiment with that code, run `bin/console` for an interactive prompt.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
5
  ## Installation
8
6
 
9
7
  Add this line to your application's Gemfile:
@@ -24,16 +22,30 @@ Or install it yourself as:
24
22
 
25
23
  ARF/XCCDF report goes IN - Ruby hash goes OUT
26
24
 
27
- {
28
- profile
29
- host
30
- score
31
- start_time
32
- end_time
33
- rule_result
25
+ ```rb
26
+ parser = OpenscapParser::Base.new(File.read('rhel7-xccdf_org.ssgproject.content_profile_standard.xml'))
27
+ parser.host # "rhel7-insights-client.virbr0.akofink-laptop"
28
+ parser.start_time # <DateTime: 2019-08-08T17:25:50+00:00 ((2458704j,62750s,0n),+0s,2299161j)>
29
+ parser.end_time # <DateTime: 2019-08-08T17:26:45+00:00 ((2458704j,62805s,0n),+0s,2299161j)>
30
+ parser.score # 80.833328
31
+ parser.profiles # {"xccdf_org.ssgproject.content_profile_standard"=>"Standard System Security Profile for Red Hat Enterprise Linux 7"}
32
+ parser.rules # [#<OpenscapParser::Rule:0x00005576e752db7 ... >, ...]
33
+ parser.rule_results # [#<OpenscapParser::RuleResult:0x00005576e8022f60 @id="xccdf_org.ssgproject.content_rule_package_rsh_removed", @result="notselected">, ...]
34
+
35
+ # and more!
36
+ ```
37
+
38
+ ### Fetching SCAP Security Guide Content
34
39
 
35
- }
40
+ This gem includes a rake task to sync content from the [ComplianceAsCode project](https://github.com/ComplianceAsCode/content). The following examples show how to download and exract datastream files from the released versions:
41
+
42
+ ```sh
43
+ rake ssg:sync DATASTREAMS=latest:fedora # fetch and extract the latest fedora datastream
44
+ rake ssg:sync DATASTREAMS=v0.1.45:fedora,v0.1.45:firefox # fetch and extract tag v0.1.45 for fedora and firefox datastreams
45
+ rake ssg:sync_rhel # fetch and extract the latest released versions of the RHEL 6, 7, and 8 datastreams
46
+ ```
36
47
 
48
+ An SSG version will be downloaded only once, even if it is specified multiple times for multiple datastreams.
37
49
 
38
50
  ## Development
39
51
 
@@ -41,6 +53,16 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
41
53
 
42
54
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
43
55
 
56
+ ### With Docker
57
+
58
+ A Dockerfile is provided to allow a containerized development environment:
59
+
60
+ ```
61
+ docker build . -t openscap_parser # build the container image
62
+ docker run -itv $PWD:/app:z openscap_parser rake # run tests
63
+ docker run -itv $PWD:/app:z openscap_parser pry --gem # console
64
+ ```
65
+
44
66
  ## Contributing
45
67
 
46
68
  Bug reports and pull requests are welcome on GitHub at https://github.com/elobato/openscap_parser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
+ import "./lib/tasks/ssg.rake"
5
+
4
6
  Rake::TestTask.new(:test) do |t|
5
7
  t.libs << "test"
6
8
  t.libs << "lib"
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/util'
4
+ require 'openscap_parser/xml_file'
5
+ require 'openscap_parser/rules'
6
+ require 'openscap_parser/profiles'
7
+ require 'openscap_parser/rule_references'
8
+
9
+ # Mimics openscap-ruby Benchmark interface
10
+ module OpenscapParser
11
+ class Benchmark < XmlNode
12
+ include OpenscapParser::Util
13
+ include OpenscapParser::Rules
14
+ include OpenscapParser::RuleReferences
15
+ include OpenscapParser::Profiles
16
+
17
+ def id
18
+ @id ||= @parsed_xml['id']
19
+ end
20
+
21
+ def title
22
+ @title ||= @parsed_xml.xpath('title') &&
23
+ @parsed_xml.xpath('title').text
24
+ end
25
+
26
+ def description
27
+ @description ||= newline_to_whitespace(
28
+ @parsed_xml.xpath('description') &&
29
+ @parsed_xml.xpath('description').text || ''
30
+ )
31
+ end
32
+
33
+ def version
34
+ @version ||= @parsed_xml.xpath('version') &&
35
+ @parsed_xml.xpath('version').text
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/benchmark'
4
+
5
+ module OpenscapParser
6
+ # Methods related to saving profiles and finding which hosts
7
+ # they belong to
8
+ module Benchmarks
9
+ def self.included(base)
10
+ base.class_eval do
11
+ def benchmark
12
+ @benchmark ||= OpenscapParser::Benchmark.new(parsed_xml: benchmark_node)
13
+ end
14
+
15
+ def benchmark_node(xpath = ".//Benchmark")
16
+ xpath_node(xpath)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require 'openscap_parser/xml_file'
3
+ require 'openscap_parser/benchmarks'
4
+
5
+ module OpenscapParser
6
+ # A class to represent a datastream (-ds.xml) XmlFile
7
+ class DatastreamFile < XmlFile
8
+ include OpenscapParser::Benchmarks
9
+
10
+ def valid?
11
+ return true if @parsed_xml.root.name == 'data-stream-collection' && namespaces.keys.include?('xmlns:ds')
12
+ false
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,35 @@
1
1
  module OpenscapParser
2
- class Profile
3
- attr_acessor :id, :title, :description
2
+ class Profile < XmlNode
3
+ def id
4
+ @id ||= @parsed_xml['id']
5
+ end
6
+
7
+ def extends_profile_id
8
+ @extends ||= @parsed_xml['extends']
9
+ end
10
+
11
+ def title
12
+ @title ||= @parsed_xml.at_css('title') &&
13
+ @parsed_xml.at_css('title').text
14
+ end
15
+ alias :name :title
16
+
17
+ def description
18
+ @description ||= @parsed_xml.at_css('description') &&
19
+ @parsed_xml.at_css('description').text
20
+ end
21
+
22
+ def selected_rule_ids
23
+ @selected_rule_ids ||= @parsed_xml.xpath("select[@selected='true']/@idref") &&
24
+ @parsed_xml.xpath("select[@selected='true']/@idref").map(&:text)
25
+ end
26
+
27
+ def set_values
28
+ @set_values ||= @parsed_xml.xpath("set-value") &&
29
+ @parsed_xml.xpath("set-value").map do |set_value|
30
+ [set_value['idref'], set_value.text]
31
+ end.to_h
32
+ end
4
33
 
5
34
  def to_h
6
35
  { :id => id, :title => title, :description => description }
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'openscap_parser/profile'
4
+
3
5
  module OpenscapParser
4
6
  # Methods related to saving profiles and finding which hosts
5
7
  # they belong to
@@ -7,20 +9,13 @@ module OpenscapParser
7
9
  def self.included(base)
8
10
  base.class_eval do
9
11
  def profiles
10
- @profiles ||= {
11
- profile_node['id'] => profile_node.at_css('title').text
12
- }
13
- end
14
-
15
- private
16
-
17
- def profile_node
18
- @report_xml.at_xpath(".//Profile\
19
- [contains('#{test_result_node['id']}', @id)]")
12
+ @profiles ||= profile_nodes.map do |profile_node|
13
+ OpenscapParser::Profile.new(parsed_xml: profile_node)
14
+ end
20
15
  end
21
16
 
22
- def test_result_node
23
- @test_result_node ||= @report_xml.at_css('TestResult')
17
+ def profile_nodes(xpath = ".//Profile")
18
+ xpath_nodes(xpath)
24
19
  end
25
20
  end
26
21
  end
@@ -1,44 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'openscap_parser/rule_identifier'
4
+ require 'openscap_parser/rule_references'
5
+ require 'openscap_parser/xml_file'
6
+
3
7
  # Mimics openscap-ruby Rule interface
4
8
  module OpenscapParser
5
- class Rule
6
- def initialize(rule_xml: nil)
7
- @rule_xml = rule_xml
8
- end
9
+ class Rule < XmlNode
10
+ include OpenscapParser::Util
11
+ include OpenscapParser::RuleReferences
9
12
 
10
13
  def id
11
- @id ||= @rule_xml['id']
14
+ @id ||= parsed_xml['id']
15
+ end
16
+
17
+ def selected
18
+ @selected ||= parsed_xml['selected']
12
19
  end
13
20
 
14
21
  def severity
15
- @severity ||= @rule_xml['severity']
22
+ @severity ||= parsed_xml['severity']
16
23
  end
17
24
 
18
25
  def title
19
- @title ||= @rule_xml.at_css('title').children.first.text
26
+ @title ||= parsed_xml.at_css('title') &&
27
+ parsed_xml.at_css('title').text
20
28
  end
21
29
 
22
30
  def description
23
- @description ||= @rule_xml.at_css('description').text.delete("\n")
31
+ @description ||= newline_to_whitespace(
32
+ parsed_xml.at_css('description') &&
33
+ parsed_xml.at_css('description').text || ''
34
+ )
24
35
  end
25
36
 
26
37
  def rationale
27
- @rationale ||= @rule_xml.at_css('rationale').children.text.delete("\n")
38
+ @rationale ||= newline_to_whitespace(
39
+ parsed_xml.at_css('rationale') &&
40
+ parsed_xml.at_css('rationale').text || ''
41
+ )
28
42
  end
29
43
 
30
- def references
31
- @references ||= @rule_xml.css('reference').map do |node|
32
- { href: node['href'], label: node.text }
33
- end
44
+ alias :rule_reference_nodes_old :rule_reference_nodes
45
+ def rule_reference_nodes(xpath = "reference")
46
+ rule_reference_nodes_old(xpath)
34
47
  end
35
48
 
36
- def identifier
37
- @identifier ||= {
38
- label: @rule_xml.at_css('ident')&.text,
39
- system: (ident = @rule_xml.at_css('ident')) && ident['system']
40
- }
49
+ def rule_identifier
50
+ @identifier ||= RuleIdentifier.new(parsed_xml: identifier_node)
51
+ end
52
+ alias :identifier :rule_identifier
53
+
54
+ def identifier_node
55
+ @identifier_node ||= parsed_xml.at_xpath('ident')
41
56
  end
42
57
  end
43
58
  end
44
-
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RuleIdentifier interface as an object
4
+ module OpenscapParser
5
+ class RuleIdentifier < XmlNode
6
+ def label
7
+ @label ||= @parsed_xml && @parsed_xml.text
8
+ end
9
+
10
+ def system
11
+ @system ||= @parsed_xml && @parsed_xml['system']
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RuleReference interface as an object
4
+ module OpenscapParser
5
+ class RuleReference < XmlNode
6
+ def href
7
+ @href ||= @parsed_xml && @parsed_xml['href']
8
+ end
9
+
10
+ def label
11
+ @label ||= @parsed_xml && @parsed_xml.text
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/xml_file'
4
+ require 'openscap_parser/rule_reference'
5
+
6
+ module OpenscapParser
7
+ # Methods related to finding and saving rule references
8
+ module RuleReferences
9
+ def self.included(base)
10
+ base.class_eval do
11
+ def rule_reference_strings
12
+ @rule_reference_strings ||= rule_references.map do |rr|
13
+ "#{rr.label}#{rr.href}"
14
+ end
15
+ end
16
+
17
+ def rule_references
18
+ @rule_references ||= rule_reference_nodes.map do |node|
19
+ OpenscapParser::RuleReference.new(parsed_xml: node)
20
+ end.uniq do |reference|
21
+ [reference.label, reference.href]
22
+ end
23
+ end
24
+ alias :references :rule_references
25
+
26
+ def rule_reference_nodes(xpath = ".//Rule/reference")
27
+ xpath_nodes(xpath)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,8 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenscapParser
4
- class RuleResult
5
- attr_accessor :id, :result
4
+ class RuleResult < XmlNode
5
+ def id
6
+ @id ||= parsed_xml['idref']
7
+ end
8
+
9
+ def time
10
+ @time ||= parsed_xml['time']
11
+ end
12
+
13
+ def severity
14
+ @severity ||= parsed_xml['severity']
15
+ end
16
+
17
+ def weight
18
+ @weight ||= parsed_xml['weight']
19
+ end
20
+
21
+ def result
22
+ @result ||= parsed_xml.at_xpath('result') &&
23
+ parsed_xml.at_xpath('result').text || ''
24
+ end
6
25
  end
7
26
  end
8
27
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/rule_result'
4
+
5
+ module OpenscapParser
6
+ module RuleResults
7
+ def self.included(base)
8
+ base.class_eval do
9
+ def rule_result_nodes
10
+ @rule_result_nodes ||= parsed_xml.xpath('rule-result')
11
+ end
12
+
13
+ def rule_results
14
+ rule_result_nodes.map do |node|
15
+ RuleResult.new(parsed_xml: node)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'openscap_parser/rule'
4
+
3
5
  module OpenscapParser
4
6
  # Methods related to parsing rules
5
7
  module Rules
6
8
  def self.included(base)
7
9
  base.class_eval do
8
- def rule_ids
9
- test_result_node.xpath('.//rule-result/@idref').map(&:value)
10
- end
11
-
12
10
  def rule_objects
13
- return @rule_objects unless @rule_objects.nil?
14
-
15
- @rule_objects ||= @report_xml.search('Rule').map do |rule|
16
- Rule.new(rule_xml: rule)
11
+ @rule_objects ||= rule_nodes.map do |rule_node|
12
+ Rule.new(parsed_xml: rule_node)
17
13
  end
18
14
  end
15
+ alias :rules :rule_objects
16
+
17
+ def rule_nodes(xpath = ".//Rule")
18
+ xpath_nodes(xpath)
19
+ end
19
20
  end
20
21
  end
21
22
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Mimics openscap-ruby Rule interface
4
+ module OpenscapParser
5
+ class Tailoring < XmlNode
6
+ include OpenscapParser::Profiles
7
+
8
+ def id
9
+ @id ||= @parsed_xml['id']
10
+ end
11
+
12
+ def benchmark
13
+ @benchmark ||= @parsed_xml.at_xpath('benchmark/@href') &&
14
+ @parsed_xml.at_xpath('benchmark/@href').text
15
+ end
16
+
17
+ def version
18
+ @version ||= @parsed_xml.at_xpath('version') &&
19
+ @parsed_xml.at_xpath('version').text
20
+ end
21
+
22
+ def version_time
23
+ @version_time ||= @parsed_xml.at_xpath('version/@time') &&
24
+ @parsed_xml.at_xpath('version/@time').text
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenscapParser
4
+ # A class to represent a tailoring XmlFile
5
+ class TailoringFile < XmlFile
6
+ include OpenscapParser::Tailorings
7
+
8
+ def valid?
9
+ return true if @parsed_xml.root.name == 'Tailoring' && namespaces.keys.include?('xmlns:xccdf')
10
+ false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/tailoring'
4
+
5
+ module OpenscapParser
6
+ # Methods related to parsing Xccdf Tailoring types
7
+ module Tailorings
8
+ def self.included(base)
9
+ base.class_eval do
10
+ def tailoring
11
+ @tailoring ||= OpenscapParser::Tailoring.new(
12
+ parsed_xml: tailoring_node
13
+ )
14
+ end
15
+
16
+ def tailoring_node(xpath = ".//Tailoring")
17
+ xpath_node(xpath)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/rule_results'
4
+
5
+ module OpenscapParser
6
+ class TestResult < XmlNode
7
+ include OpenscapParser::RuleResults
8
+
9
+ def target
10
+ @target ||= parsed_xml.at_xpath('target') &&
11
+ parsed_xml.at_xpath('target').text || ''
12
+ end
13
+ alias :host :target
14
+
15
+ def target_fact_nodes
16
+ @target_fact_nodes ||= parsed_xml.xpath('target-facts/fact')
17
+ end
18
+
19
+ def platform_nodes
20
+ @platform_nodes ||= parsed_xml.xpath('platform')
21
+ end
22
+
23
+ def title
24
+ @title ||= parsed_xml.at_xpath('title') &&
25
+ parsed_xml.at_xpath('title').text || ''
26
+ end
27
+
28
+ def identity
29
+ @identity ||= parsed_xml.at_xpath('identity') &&
30
+ parsed_xml.at_xpath('identity').text || ''
31
+ end
32
+
33
+ def profile_id
34
+ @profile_id ||= parsed_xml.at_xpath('profile') &&
35
+ parsed_xml.at_xpath('profile')['idref'] || ''
36
+ end
37
+
38
+ def benchmark_id
39
+ @benchmark_id ||= parsed_xml.at_xpath('benchmark') &&
40
+ parsed_xml.at_xpath('benchmark')['id'] || ''
41
+ end
42
+
43
+ def set_value_nodes
44
+ @set_value_nodes ||= parsed_xml.xpath('set-value')
45
+ end
46
+
47
+ def score
48
+ @score ||= parsed_xml.at_xpath('score') &&
49
+ parsed_xml.at_xpath('score').text.to_f
50
+ end
51
+
52
+ def start_time
53
+ @start_time ||= DateTime.parse(parsed_xml['start-time'])
54
+ end
55
+
56
+ def end_time
57
+ @end_time ||= DateTime.parse(parsed_xml['end-time'])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenscapParser
4
+ # A class to represent an XmlFile which contains a <TestResult /> Xccdf type
5
+ class TestResultFile < XmlFile
6
+ include ::OpenscapParser::Benchmarks
7
+ include ::OpenscapParser::TestResults
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/test_result'
4
+
5
+ module OpenscapParser
6
+ module TestResults
7
+ def self.included(base)
8
+ base.class_eval do
9
+ def test_result
10
+ TestResult.new(parsed_xml: test_result_node)
11
+ end
12
+
13
+ def test_result_node
14
+ @test_result_node ||= xpath_node('.//TestResult')
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Utility functions for OpenscapParser
4
+ module OpenscapParser
5
+ module Util
6
+ def newline_to_whitespace(string)
7
+ string.gsub(/ *\n+/, " ").strip
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module OpenscapParser
2
- VERSION = "0.1.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'nokogiri'
4
+ require 'openscap_parser/xml_node'
3
5
 
4
6
  module OpenscapParser
5
- module XmlFile
6
- def report_xml(report_contents = '')
7
- @report_xml ||= ::Nokogiri::XML.parse(report_contents)
8
- @report_xml.remove_namespaces!
7
+ class XmlFile < XmlNode
8
+
9
+ def initialize(raw_xml)
10
+ parsed_xml(raw_xml)
9
11
  end
10
12
  end
11
13
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module OpenscapParser
6
+ # Represents a generic Xml node with parsed_xml
7
+ class XmlNode
8
+ attr_reader :namespaces
9
+
10
+ def initialize(parsed_xml: nil)
11
+ @parsed_xml = parsed_xml
12
+ end
13
+
14
+ def parsed_xml(report_contents = '')
15
+ return @parsed_xml if @parsed_xml
16
+ @parsed_xml = ::Nokogiri::XML.parse(
17
+ report_contents, nil, nil, Nokogiri::XML::ParseOptions.new.norecover)
18
+ @namespaces = @parsed_xml.namespaces.clone
19
+ @parsed_xml.remove_namespaces!
20
+ end
21
+
22
+ def xpath_node(xpath)
23
+ parsed_xml && parsed_xml.at_xpath(xpath)
24
+ end
25
+ alias :at_xpath :xpath_node
26
+
27
+ def xpath_nodes(xpath)
28
+ parsed_xml && parsed_xml.xpath(xpath) || []
29
+ end
30
+ alias :xpath :xpath_nodes
31
+ end
32
+ end
@@ -1,45 +1,22 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'openscap_parser/version'
4
+ require 'openscap_parser/util'
5
+ require 'openscap_parser/benchmarks'
6
+ require 'openscap_parser/test_results'
2
7
  require 'openscap_parser/profiles'
3
- require 'openscap_parser/rule'
4
- require 'openscap_parser/rule_result'
5
8
  require 'openscap_parser/rules'
6
- require 'openscap_parser/version'
7
- require 'openscap_parser/xml_report'
8
- require 'openscap_parser/ds'
9
+ require 'openscap_parser/rule_results'
10
+ require 'openscap_parser/tailorings'
11
+
12
+ require 'openscap_parser/xml_file'
13
+ require 'openscap_parser/datastream_file'
14
+ require 'openscap_parser/test_result_file'
15
+ require 'openscap_parser/tailoring_file'
9
16
 
10
17
  require 'date'
18
+ require 'railtie' if defined?(Rails)
11
19
 
12
20
  module OpenscapParser
13
21
  class Error < StandardError; end
14
-
15
- class Base
16
- include OpenscapParser::XMLReport
17
- include OpenscapParser::Profiles
18
- include OpenscapParser::Rules
19
-
20
- def initialize(report)
21
- report_xml(report)
22
- end
23
-
24
- def score
25
- test_result_node.search('score').text.to_f
26
- end
27
-
28
- def start_time
29
- @start_time ||= DateTime.parse(test_result_node['start-time'])
30
- end
31
-
32
- def end_time
33
- @end_time ||= DateTime.parse(test_result_node['end-time'])
34
- end
35
-
36
- def rule_results
37
- @rule_results ||= test_result_node.search('rule-result').map do |rr|
38
- rule_result_oscap = RuleResult.new
39
- rule_result_oscap.id = rr.attributes['idref'].value
40
- rule_result_oscap.result = rr.search('result').first.text
41
- rule_result_oscap
42
- end
43
- end
44
- end
45
22
  end
data/lib/railtie.rb ADDED
@@ -0,0 +1,15 @@
1
+ # lib/railtie.rb
2
+ require 'openscap_parser'
3
+
4
+ if defined?(Rails)
5
+ module OpenscapParser
6
+ class Railtie < Rails::Railtie
7
+ railtie_name :openscap_parser
8
+
9
+ rake_tasks do
10
+ path = File.expand_path(__dir__)
11
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module Ssg
7
+ # Downloads SCAP datastreams from the SCAP Security Guide
8
+ # https://github.com/ComplianceAsCode/content
9
+ class Downloader
10
+ RELEASES_API = 'https://api.github.com/repos'\
11
+ '/ComplianceAsCode/content/releases/'
12
+ SSG_DS_REGEX = /scap-security-guide-(\d+\.)+zip$/
13
+
14
+ def initialize(version = 'latest')
15
+ @release_uri = URI(
16
+ "#{RELEASES_API}#{'tags/' unless version[/^latest$/]}#{version}"
17
+ )
18
+ end
19
+
20
+ def self.download!(versions = [])
21
+ versions.uniq.map do |version|
22
+ [version, new(version).fetch_datastream_file]
23
+ end.to_h
24
+ end
25
+
26
+ def fetch_datastream_file
27
+ puts "Fetching #{datastream_filename}"
28
+ get_chunked(datastream_uri)
29
+
30
+ datastream_filename
31
+ end
32
+
33
+ private
34
+
35
+ def datastream_uri
36
+ @datastream_uri ||= URI(
37
+ download_urls.find { |url| url[SSG_DS_REGEX] }
38
+ )
39
+ end
40
+
41
+ def download_urls
42
+ get_json(@release_uri).dig('assets').map do |asset|
43
+ asset.dig('browser_download_url')
44
+ end
45
+ end
46
+
47
+ def fetch(request, &block)
48
+ Net::HTTP.start(
49
+ request.uri.host, request.uri.port,
50
+ use_ssl: request.uri.scheme['https']
51
+ ) do |http|
52
+ check_response(http.request(request, &block), &block)
53
+ end
54
+ end
55
+
56
+ def get(uri, &block)
57
+ fetch(Net::HTTP::Get.new(uri), &block)
58
+ end
59
+
60
+ def head(uri, &block)
61
+ fetch(Net::HTTP::Head.new(uri), &block)
62
+ end
63
+
64
+ def check_response(response, &block)
65
+ case response
66
+ when Net::HTTPSuccess
67
+ response
68
+ when Net::HTTPRedirection
69
+ get(URI(response['location']), &block)
70
+ else
71
+ response.value
72
+ end
73
+ end
74
+
75
+ def get_chunked(uri, filename: datastream_filename)
76
+ head(uri) do |response|
77
+ next unless Net::HTTPSuccess === response
78
+ open(filename, 'wb') do |file|
79
+ response.read_body do |chunk|
80
+ file.write(chunk)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def datastream_filename
87
+ datastream_uri.path.split('/').last[SSG_DS_REGEX]
88
+ end
89
+
90
+ def get_json(uri)
91
+ JSON.parse(get(uri).body)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,34 @@
1
+ module Ssg
2
+ class Unarchiver
3
+ UNZIP_CMD = ['unzip', '-o']
4
+
5
+ def initialize(ds_zip_filename, datastreams)
6
+ @ds_zip_filename = ds_zip_filename
7
+ @datastreams = datastreams
8
+ end
9
+
10
+ def self.unarchive!(ds_zip_filenames, datastreams)
11
+ ds_zip_filenames.map do |version, ds_zip_filename|
12
+ new(ds_zip_filename, [datastreams[version]].flatten).datastream_files
13
+ end
14
+ end
15
+
16
+ def datastream_files
17
+ datastream_filenames if system(
18
+ *UNZIP_CMD, @ds_zip_filename, *datastream_filenames
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def datastream_filenames
25
+ @datastreams.map do |datastream|
26
+ "#{datastream_dir}/ssg-#{datastream}-ds.xml"
27
+ end
28
+ end
29
+
30
+ def datastream_dir
31
+ @ds_zip_filename.split('.')[0...-1].join('.')
32
+ end
33
+ end
34
+ end
data/lib/ssg.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'ssg/downloader'
2
+ require 'ssg/unarchiver'
3
+
4
+ module Ssg
5
+ end
@@ -0,0 +1,33 @@
1
+ desc 'Import or update SCAP datastreams from the SCAP Security Guide'
2
+ namespace :ssg do
3
+ desc 'Import or update SCAP datastreams for RHEL 6, 7, and 8'
4
+ task :sync_rhel do |task|
5
+ RHEL_SSG_VERSIONS = (
6
+ 'v0.1.28:rhel6,'\
7
+ 'v0.1.43:rhel7,'\
8
+ 'v0.1.42:rhel8'
9
+ )
10
+
11
+ ENV['DATASTREAMS'] = RHEL_SSG_VERSIONS
12
+ Rake::Task['ssg:sync'].invoke
13
+ end
14
+
15
+ desc 'Import or update SCAP datastreams, '\
16
+ 'provided as a comma separated list: '\
17
+ '`rake ssg:sync DATASTREAMS=v0.1.43:rhel7,latest:fedora`'
18
+ task :sync do |task|
19
+ DATASTREAMS = ENV.fetch('DATASTREAMS', '').split(',')
20
+ .inject({}) do |datastreams, arg|
21
+ version, datastream = arg.split(':')
22
+ datastreams[version] = (datastreams[version] || []).push(datastream)
23
+
24
+ datastreams
25
+ end
26
+
27
+ require 'ssg'
28
+
29
+ ds_zip_filenames = Ssg::Downloader.download!(DATASTREAMS.keys)
30
+ DATASTREAM_FILENAMES = Ssg::Unarchiver.
31
+ unarchive!(ds_zip_filenames, DATASTREAMS)
32
+ end
33
+ end
@@ -4,15 +4,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require "openscap_parser/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "openscap_parser"
7
+ spec.name = 'openscap_parser'
8
8
  spec.version = OpenscapParser::VERSION
9
- spec.authors = ["Daniel Lobato Garcia"]
10
- spec.email = ["me@daniellobato.me"]
9
+ spec.authors = ['Daniel Lobato Garcia', 'Andrew Kofink']
10
+ spec.email = ['me@daniellobato.me', 'ajkofink@gmail.com']
11
11
 
12
- spec.summary = %q{Parse OpenSCAP reports}
13
- # spec.description = %q{TODO: Write a longer description or delete this line.}
14
- # spec.homepage = "TODO: Put your gem's website or public repo URL here."
15
- spec.license = "MIT"
12
+ spec.summary = %q{Parse OpenSCAP content}
13
+ spec.description = %q{This gem is a Ruby interface into SCAP content. It can parse SCAP datastream files (i.e. ssg-rhel7-ds.xml), scan result files output by oscap eval, and tailoring files.}
14
+ spec.homepage = 'https://github.com/dLobatog/openscap_parser'
15
+ spec.license = 'MIT'
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
18
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -32,13 +32,16 @@ Gem::Specification.new do |spec|
32
32
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
33
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
34
  end
35
- spec.bindir = "exe"
36
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
+ spec.bindir = "bin"
36
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
37
37
  spec.require_paths = ["lib"]
38
38
 
39
- spec.add_dependency 'nokogiri'
39
+ spec.add_dependency "nokogiri", "~> 1.6"
40
40
  spec.add_development_dependency "bundler", "~> 2.0"
41
41
  spec.add_development_dependency "rake", "~> 10.0"
42
42
  spec.add_development_dependency "minitest", "~> 5.0"
43
+ spec.add_development_dependency "mocha", "~> 1.0"
43
44
  spec.add_development_dependency "shoulda-context"
45
+ spec.add_development_dependency "pry"
46
+ spec.add_development_dependency "pry-byebug"
44
47
  end
metadata CHANGED
@@ -1,29 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openscap_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Lobato Garcia
8
+ - Andrew Kofink
8
9
  autorequire:
9
- bindir: exe
10
+ bindir: bin
10
11
  cert_chain: []
11
- date: 2019-06-12 00:00:00.000000000 Z
12
+ date: 2019-10-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: nokogiri
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - ">="
18
+ - - "~>"
18
19
  - !ruby/object:Gem::Version
19
- version: '0'
20
+ version: '1.6'
20
21
  type: :runtime
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - ">="
25
+ - - "~>"
25
26
  - !ruby/object:Gem::Version
26
- version: '0'
27
+ version: '1.6'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: bundler
29
30
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +67,20 @@ dependencies:
66
67
  - - "~>"
67
68
  - !ruby/object:Gem::Version
68
69
  version: '5.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: mocha
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.0'
69
84
  - !ruby/object:Gem::Dependency
70
85
  name: shoulda-context
71
86
  requirement: !ruby/object:Gem::Requirement
@@ -80,16 +95,50 @@ dependencies:
80
95
  - - ">="
81
96
  - !ruby/object:Gem::Version
82
97
  version: '0'
83
- description:
98
+ - !ruby/object:Gem::Dependency
99
+ name: pry
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: pry-byebug
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: This gem is a Ruby interface into SCAP content. It can parse SCAP datastream
127
+ files (i.e. ssg-rhel7-ds.xml), scan result files output by oscap eval, and tailoring
128
+ files.
84
129
  email:
85
130
  - me@daniellobato.me
86
- executables: []
131
+ - ajkofink@gmail.com
132
+ executables:
133
+ - console
134
+ - setup
87
135
  extensions: []
88
136
  extra_rdoc_files: []
89
137
  files:
90
138
  - ".gitignore"
91
139
  - ".travis.yml"
92
140
  - CODE_OF_CONDUCT.md
141
+ - Dockerfile
93
142
  - Gemfile
94
143
  - LICENSE.txt
95
144
  - README.md
@@ -97,17 +146,35 @@ files:
97
146
  - bin/console
98
147
  - bin/setup
99
148
  - lib/openscap_parser.rb
100
- - lib/openscap_parser/ds.rb
149
+ - lib/openscap_parser/benchmark.rb
150
+ - lib/openscap_parser/benchmarks.rb
151
+ - lib/openscap_parser/datastream_file.rb
101
152
  - lib/openscap_parser/profile.rb
102
153
  - lib/openscap_parser/profiles.rb
103
154
  - lib/openscap_parser/rule.rb
155
+ - lib/openscap_parser/rule_identifier.rb
156
+ - lib/openscap_parser/rule_reference.rb
157
+ - lib/openscap_parser/rule_references.rb
104
158
  - lib/openscap_parser/rule_result.rb
159
+ - lib/openscap_parser/rule_results.rb
105
160
  - lib/openscap_parser/rules.rb
161
+ - lib/openscap_parser/tailoring.rb
162
+ - lib/openscap_parser/tailoring_file.rb
163
+ - lib/openscap_parser/tailorings.rb
164
+ - lib/openscap_parser/test_result.rb
165
+ - lib/openscap_parser/test_result_file.rb
166
+ - lib/openscap_parser/test_results.rb
167
+ - lib/openscap_parser/util.rb
106
168
  - lib/openscap_parser/version.rb
107
169
  - lib/openscap_parser/xml_file.rb
108
- - lib/openscap_parser/xml_report.rb
170
+ - lib/openscap_parser/xml_node.rb
171
+ - lib/railtie.rb
172
+ - lib/ssg.rb
173
+ - lib/ssg/downloader.rb
174
+ - lib/ssg/unarchiver.rb
175
+ - lib/tasks/ssg.rake
109
176
  - openscap_parser.gemspec
110
- homepage:
177
+ homepage: https://github.com/dLobatog/openscap_parser
111
178
  licenses:
112
179
  - MIT
113
180
  metadata: {}
@@ -126,9 +193,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
193
  - !ruby/object:Gem::Version
127
194
  version: '0'
128
195
  requirements: []
129
- rubyforge_project:
130
- rubygems_version: 2.7.6
196
+ rubygems_version: 3.0.6
131
197
  signing_key:
132
198
  specification_version: 4
133
- summary: Parse OpenSCAP reports
199
+ summary: Parse OpenSCAP content
134
200
  test_files: []
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'openscap_parser/xml_file'
3
-
4
- module OpenscapParser
5
- class Ds
6
- include OpenscapParser::XmlFile
7
-
8
- def initialize(report)
9
- report_xml report
10
- end
11
-
12
- def profiles
13
- @profiles ||= profile_nodes
14
- end
15
-
16
- private
17
-
18
- def profile_nodes
19
- @report_xml.xpath(".//Profile").map do |node|
20
- id = node.attribute('id')&.value
21
- title = node.at_xpath('./title')&.text
22
- description = node.at_xpath('./description')&.text
23
- { :id => id, :title => title, :description => description }
24
- end
25
- end
26
- end
27
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'nokogiri'
3
- require 'openscap_parser/xml_file'
4
-
5
- module OpenscapParser
6
- # Methods related with parsing directly the XML from the Report
7
- # as opposed to using the OpenSCAP APIs
8
- module XMLReport
9
- def self.included(base)
10
- base.class_eval do
11
- include OpenscapParser::XmlFile
12
-
13
- def host
14
- @report_xml.search('target').text
15
- end
16
-
17
- def description
18
- @report_xml.search('description').first.text
19
- end
20
- end
21
- end
22
- end
23
- end