openscap_parser 0.1.2 → 1.0.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.
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