inspec_tools 2.0.7 → 2.1.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: c551987d4bfca9ae4d14630f4beb3f4dd6cbe78314a2f229d5df49b4c72cd0e2
4
- data.tar.gz: d0553cb380d6103f21b879fdb43d39c502ba94d80cc6b30ae8c69cf246464f4d
3
+ metadata.gz: eee290c08f68bf8bb7894537a1871e0bdf5144266ee50932ea70af3018d5f94e
4
+ data.tar.gz: 42ed4ee43680aaceb6fa12c03bfe7c29dd88a33399787b7053510716182c627e
5
5
  SHA512:
6
- metadata.gz: ffb7fa2a36d380a08c193fc0b1295c74a9479ef54ac5085cf3f36072509d67d235603c6fb589e81ce6c1d52544793fab3b91d37153e303923e7c99bb843f4510
7
- data.tar.gz: 8e10eacec2b81f6a7c57198cc817050ee501ede1a390e347be50c80429d3834d506eb1f417b9f659ef0c620240d36e15cd65e118ea59fbbaa5481131cba4eb38
6
+ metadata.gz: 43e9b4038ff40f6aee88a16fb63e536b7d3918f15b8a3f9b3fcf87688eb1a167cfd08a7f45cac6fcdd68a2cdd1b5f1ac9ab9a85a2bd42c983b9e963c0b18d96d
7
+ data.tar.gz: cd750538af91a05636ad406b80f1adc83bbacfa382143d472bbced9265227f3da245a8a23dd7de0af3636c1650b1c563c6621dc48f524db1550518e7bcc55ae5
data/README.md CHANGED
@@ -70,7 +70,7 @@ Note that all of the above Docker commands will mount your current directory on
70
70
 
71
71
  ### generate_map
72
72
 
73
- This command will generate a `mapping.xml` file that can be passed in to the `csv2inspec` command with the `--m` option.
73
+ This command will generate a `mapping.yml` file that can be passed in to the `csv2inspec` command with the `--m` option.
74
74
 
75
75
  ```
76
76
  USAGE: inspec_tools generate_map
@@ -220,16 +220,18 @@ example: inspec_tools xccdf2inspec -x xccdf_file.xml -a attributes.yml -o myprof
220
220
 
221
221
  `inspec2xccdf` converts an InSpec profile in json format to a STIG XCCDF Document
222
222
 
223
+ See [examples documentation](./examples/inspec2xccdf/README.md) for additional guidance on usage including attribute details.
224
+
223
225
  ```
224
226
  USAGE: inspec_tools inspec2xccdf [OPTIONS] -j <inspec-json> -a <xccdf-attr-yml> -o <xccdf-xml>
225
227
 
226
228
  FLAGS:
227
229
  -j --inspec-json <inspec-json> : path to InSpec Json file created using command 'inspec json <profile> > example.json'
228
- -a --attributes <xccdf-attr-yml> : path to yml file that provides the required attributes for the XCCDF Document. these attributes are parts of XCCDF document which do not fit into the InSpec schema
229
- -o --output <xccdf-xml> : name or path to create the xccdf and title to give the xccdf
230
- -V --verbose : verbose run [optional]
230
+ -a --attributes <xccdf-attr-yml> : path to yml file that provides the required attributes for the XCCDF document. These attributes are parts of XCCDF document which do not fit into the InSpec schema.
231
+ -o --output <xccdf-xml> : name or path to create the XCCDF and title to give the XCCDF
232
+ -m, [--metadata=METADATA] : path to json file with additional host metadata for the XCCDF file
231
233
 
232
- example: inspec_tools inspec2xccdf -j example.json -a attributes.yml -o xccdf.xml
234
+ example: inspec_tools inspec2xccdf -j examples/sample_json/good_nginxresults.json -a lib/data/attributes.yml -o output.xccdf
233
235
  ```
234
236
 
235
237
  ## csv2inspec
data/Rakefile CHANGED
@@ -19,7 +19,15 @@ namespace :test do
19
19
  'test/unit/inspec_tools_test.rb'
20
20
  ])
21
21
  end
22
- end
22
+
23
+ Rake::TestTask.new(:exclude_slow) do |t|
24
+ t.description = 'Excluding all tests that take more than 3 seconds to complete'
25
+ t.libs << 'test'
26
+ t.libs << "lib"
27
+ t.verbose = true
28
+ t.test_files = FileList['test/**/*_test.rb'].reject{|file| file.include? 'pdf_test.rb'}.reverse
29
+ end
30
+ end
23
31
 
24
32
  desc 'Build for release'
25
33
  task :build_release do
@@ -99,6 +99,88 @@ module HappyMapperTools
99
99
  element :content, String, tag: 'check-content'
100
100
  end
101
101
 
102
+ class MessageType
103
+ include HappyMapper
104
+ attribute :severity, String, tag: 'severity'
105
+ content :message, String
106
+ end
107
+
108
+ class RuleResultType
109
+ include HappyMapper
110
+ attribute :idref, String, tag: 'idref'
111
+ attribute :severity, String, tag: 'severity'
112
+ attribute :time, String, tag: 'time'
113
+ attribute :weight, String, tag: 'weight'
114
+ element :result, String, tag: 'result'
115
+ # element override - Not implemented. Does not apply to Inspec execution
116
+ has_many :ident, Ident, tag: 'ident'
117
+ # Note: element metadata not implemented at this time
118
+ has_many :message, MessageType, tag: 'message'
119
+ has_many :instance, String, tag: 'instance'
120
+ element :fix, Fix, tag: 'fix'
121
+ element :check, Check, tag: 'check'
122
+ end
123
+
124
+ class ScoreType
125
+ include HappyMapper
126
+
127
+ def initialize(system, maximum, score)
128
+ @system = system
129
+ @maximum = maximum
130
+ @score = score
131
+ end
132
+
133
+ attribute :system, String, tag: 'system'
134
+ attribute :maximum, String, tag: 'maximum' # optional attribute
135
+ content :score, String
136
+ end
137
+
138
+ class CPE2idrefType
139
+ include HappyMapper
140
+ attribute :idref, String, tag: 'idref'
141
+ end
142
+
143
+ class IdentityType
144
+ include HappyMapper
145
+ attribute :authenticated, Boolean, tag: 'authenticated'
146
+ attribute :privileged, Boolean, tag: 'privileged'
147
+ content :identity, String
148
+ end
149
+
150
+ class Fact
151
+ include HappyMapper
152
+ attribute :name, String, tag: 'name'
153
+ attribute :type, String, tag: 'type'
154
+ content :fact, String
155
+ end
156
+
157
+ class TargetFact
158
+ include HappyMapper
159
+ has_many :fact, Fact, tag: 'fact'
160
+ end
161
+
162
+ class TestResult
163
+ include HappyMapper
164
+ # Note: element benchmark not implemented at this time since this is same file
165
+ # Note: element title not implemented due to no mapping from Chef Inspec
166
+ element :remark, String, tag: 'remark'
167
+ has_many :organization, String, tag: 'organization'
168
+ element :identity, IdentityType, tag: 'identity'
169
+ element :target, String, tag: 'target'
170
+ has_many :target_address, String, tag: 'target-address'
171
+ element :target_facts, TargetFact, tag: 'target-facts'
172
+ element :platform, CPE2idrefType, tag: 'platform'
173
+ # Note: element profile not implemented since Benchmark profile is also not implemented
174
+ has_many :rule_result, RuleResultType, tag: 'rule-result'
175
+ has_many :score, ScoreType, tag: 'score' # One minimum
176
+ # Note: element signature not implemented due to no mapping from Chef Inspec
177
+ attribute :id, String, tag: 'id'
178
+ attribute :starttime, String, tag: 'start-time'
179
+ attribute :endtime, String, tag: 'end-time'
180
+ # Note: attribute test-system not implemented at this time due to unknown CPE value for Chef Inspec
181
+ attribute :version, String, tag: 'version'
182
+ end
183
+
102
184
  # Class Profile maps from the 'Profile' from Benchmark XML file using HappyMapper
103
185
  class Profile
104
186
  include HappyMapper
@@ -156,6 +238,7 @@ module HappyMapperTools
156
238
  element :version, String, tag: 'version'
157
239
  has_many :profile, Profile, tag: 'Profile'
158
240
  has_many :group, Group, tag: 'Group'
241
+ element :testresult, TestResult, tag: 'TestResult'
159
242
  end
160
243
  end
161
244
  end
@@ -1,10 +1,10 @@
1
1
  require 'csv'
2
- require 'nokogiri'
3
- require 'word_wrap'
4
2
  require 'yaml'
5
3
  require 'digest'
6
4
 
7
5
  require_relative '../utilities/inspec_util'
6
+ require_relative '../utilities/cci_xml'
7
+ require_relative '../utilities/mapping_validator'
8
8
 
9
9
  # rubocop:disable Metrics/AbcSize
10
10
  # rubocop:disable Metrics/PerceivedComplexity
@@ -16,34 +16,25 @@ module InspecTools
16
16
  def initialize(csv, mapping, name, verbose = false)
17
17
  @name = name
18
18
  @csv = csv
19
- @mapping = mapping
19
+ @mapping = Utils::MappingValidator.validate(mapping)
20
20
  @verbose = verbose
21
21
  @csv.shift if @mapping['skip_csv_header']
22
22
  end
23
23
 
24
- def to_ckl
25
- # TODO
26
- end
27
-
28
- def to_xccdf
29
- # TODO
30
- end
31
-
32
24
  def to_inspec
33
25
  @controls = []
34
- @cci_xml = nil
35
26
  @profile = {}
36
- read_cci_xml
37
- insert_json_metadata
27
+ @cci_xml = Utils::CciXml.get_cci_list('U_CCI_List.xml')
28
+ insert_metadata
38
29
  parse_controls
39
30
  @profile['controls'] = @controls
40
- @profile['sha256'] = Digest::SHA256.hexdigest @profile.to_s
31
+ @profile['sha256'] = Digest::SHA256.hexdigest(@profile.to_s)
41
32
  @profile
42
33
  end
43
34
 
44
35
  private
45
36
 
46
- def insert_json_metadata
37
+ def insert_metadata
47
38
  @profile['name'] = @name
48
39
  @profile['title'] = 'InSpec Profile'
49
40
  @profile['maintainer'] = 'The Authors'
@@ -60,35 +51,37 @@ module InspecTools
60
51
  }
61
52
  end
62
53
 
63
- def read_cci_xml
64
- cci_list_path = File.join(File.dirname(__FILE__), '../data/U_CCI_List.xml')
65
- @cci_xml = Nokogiri::XML(File.open(cci_list_path))
66
- @cci_xml.remove_namespaces!
67
- rescue StandardError => e
68
- puts "Exception: #{e.message}"
69
- end
70
-
71
54
  def get_nist_reference(cci_number)
72
55
  item_node = @cci_xml.xpath("//cci_list/cci_items/cci_item[@id='#{cci_number}']")[0] unless @cci_xml.nil?
73
- unless item_node.nil?
74
- nist_ref = item_node.xpath('./references/reference[not(@version <= preceding-sibling::reference/@version) and not(@version <=following-sibling::reference/@version)]/@index').text
75
- nist_ver = item_node.xpath('./references/reference[not(@version <= preceding-sibling::reference/@version) and not(@version <=following-sibling::reference/@version)]/@version').text
76
- end
77
- [nist_ref, nist_ver]
56
+ return nil if item_node.nil?
57
+
58
+ [] << item_node.xpath('./references/reference[not(@version <= preceding-sibling::reference/@version) and not(@version <=following-sibling::reference/@version)]/@index').text
59
+ end
60
+
61
+ def get_cci_number(cell)
62
+ # Return nil if a mapping to the CCI was not provided or if there is not content in the CSV cell.
63
+ return nil if cell.nil? || @mapping['control.tags']['cci'].nil?
64
+
65
+ # If the content has been exported from STIG Viewer, the cell will have extra information
66
+ cell.split("\n").first
78
67
  end
79
68
 
80
69
  def parse_controls
81
70
  @csv.each do |row|
82
- print '.'
83
71
  control = {}
84
72
  control['id'] = row[@mapping['control.id']] unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
85
73
  control['title'] = row[@mapping['control.title']] unless @mapping['control.title'].nil? || row[@mapping['control.title']].nil?
86
74
  control['desc'] = row[@mapping['control.desc']] unless @mapping['control.desc'].nil? || row[@mapping['control.desc']].nil?
87
75
  control['tags'] = {}
88
- nist, nist_rev = get_nist_reference(row[@mapping['control.tags']['cci']]) unless @mapping['control.tags']['cci'].nil? || row[@mapping['control.tags']['cci']].nil?
89
- control['tags']['nist'] = [nist, 'Rev_' + nist_rev] unless nist.nil? || nist_rev.nil?
76
+ cci_number = get_cci_number(row[@mapping['control.tags']['cci']])
77
+ nist = get_nist_reference(cci_number) unless cci_number.nil?
78
+ control['tags']['nist'] = nist unless nist.nil? || nist.include?(nil)
90
79
  @mapping['control.tags'].each do |tag|
91
- control['tags'][tag.first.to_s] = row[tag.last] unless row[tag.last].nil?
80
+ if tag.first == 'cci'
81
+ control['tags'][tag.first] = cci_number
82
+ next
83
+ end
84
+ control['tags'][tag.first] = row[tag.last] unless row[tag.last].nil?
92
85
  end
93
86
  unless @mapping['control.tags']['severity'].nil? || row[@mapping['control.tags']['severity']].nil?
94
87
  control['impact'] = Utils::InspecUtil.get_impact(row[@mapping['control.tags']['severity']])
@@ -0,0 +1,35 @@
1
+ module InspecTools
2
+ class GenerateMap
3
+ attr_accessor :text
4
+
5
+ def initialize(text = nil)
6
+ @text = text.nil? ? default_text : text
7
+ end
8
+
9
+ def generate_example(file)
10
+ File.write(file, @text)
11
+ end
12
+
13
+ private
14
+
15
+ def default_text
16
+ <<~YML
17
+ # Setting csv_header to true will skip the csv file header
18
+ skip_csv_header: true
19
+ width : 80
20
+
21
+
22
+ control.id: 0
23
+ control.title: 15
24
+ control.desc: 16
25
+ control.tags:
26
+ severity: 1
27
+ rid: 8
28
+ stig_id: 3
29
+ cci: 2
30
+ check: 12
31
+ fix: 10
32
+ YML
33
+ end
34
+ end
35
+ end
@@ -9,17 +9,14 @@ require_relative '../happy_mapper_tools/stig_checklist'
9
9
  require_relative '../happy_mapper_tools/benchmark'
10
10
  require_relative '../utilities/inspec_util'
11
11
  require_relative 'csv'
12
-
13
- # rubocop:disable Metrics/ClassLength
14
- # rubocop:disable Metrics/AbcSize
15
- # rubocop:disable Metrics/BlockLength
16
- # rubocop:disable Style/GuardClause
12
+ require_relative '../utilities/xccdf/from_inspec'
13
+ require_relative '../utilities/xccdf/to_xccdf'
17
14
 
18
15
  module InspecTools
19
- class Inspec
20
- def initialize(inspec_json, metadata = '{}')
16
+ class Inspec # rubocop:disable Metrics/ClassLength
17
+ def initialize(inspec_json, metadata = {})
21
18
  @json = JSON.parse(inspec_json.gsub(/\\+u0000/, ''))
22
- @metadata = JSON.parse(metadata)
19
+ @metadata = metadata
23
20
  end
24
21
 
25
22
  def to_ckl(title = nil, date = nil, cklist = nil)
@@ -36,16 +33,15 @@ module InspecTools
36
33
  @checklist.to_xml.encode('UTF-8').gsub('<?xml version="1.0"?>', '<?xml version="1.0" encoding="UTF-8"?>').chomp
37
34
  end
38
35
 
36
+ # Convert Inspec result data to XCCDF
37
+ #
38
+ # @param attributes [Hash] Optional input attributes
39
+ # @return [String] XML formatted String
39
40
  def to_xccdf(attributes, verbose = false)
40
- @data = Utils::InspecUtil.parse_data_for_xccdf(@json)
41
- @attribute = attributes
42
- @attribute = {} if @attribute.eql? false
41
+ data = Utils::FromInspec.new.parse_data_for_xccdf(@json)
43
42
  @verbose = verbose
44
- @benchmark = HappyMapperTools::Benchmark::Benchmark.new
45
- populate_header
46
- # populate_profiles @todo populate profiles; not implemented now because its use is deprecated
47
- populate_groups
48
- @benchmark.to_xml
43
+
44
+ Utils::ToXCCDF.new(attributes || {}, data).to_xml(@metadata)
49
45
  end
50
46
 
51
47
  ####
@@ -70,7 +66,7 @@ module InspecTools
70
66
  #
71
67
  # @param inspec_json : an inspec profile formatted as a json object
72
68
  ###
73
- def inspec_json_to_array(inspec_json)
69
+ def inspec_json_to_array(inspec_json) # rubocop:disable Metrics/CyclomaticComplexity
74
70
  data = []
75
71
  headers = {}
76
72
  inspec_json['controls'].each do |control|
@@ -97,10 +93,11 @@ module InspecTools
97
93
  @data['controls'] << control
98
94
  end
99
95
  end
100
- if json['profiles'].nil?
101
- json['controls'].each do |control|
102
- @data['controls'] << control
103
- end
96
+
97
+ return unless json['profiles'].nil?
98
+
99
+ json['controls'].each do |control|
100
+ @data['controls'] << control
104
101
  end
105
102
  end
106
103
 
@@ -161,7 +158,7 @@ module InspecTools
161
158
  vuln
162
159
  end
163
160
 
164
- def generate_asset
161
+ def generate_asset # rubocop:disable Metrics/AbcSize
165
162
  asset = HappyMapperTools::StigChecklist::Asset.new
166
163
  asset.role = !@metadata['role'].nil? ? @metadata['role'] : 'Workstation'
167
164
  asset.type = !@metadata['type'].nil? ? @metadata['type'] : 'Computing'
@@ -223,75 +220,6 @@ module InspecTools
223
220
  ip
224
221
  end
225
222
 
226
- def populate_header
227
- @benchmark.title = @attribute['benchmark.title']
228
- @benchmark.id = @attribute['benchmark.id']
229
- @benchmark.description = @attribute['benchmark.description']
230
- @benchmark.version = @attribute['benchmark.version']
231
-
232
- @benchmark.status = HappyMapperTools::Benchmark::Status.new
233
- @benchmark.status.status = @attribute['benchmark.status']
234
- @benchmark.status.date = @attribute['benchmark.status.date']
235
-
236
- @benchmark.notice = HappyMapperTools::Benchmark::Notice.new
237
- @benchmark.notice.id = @attribute['benchmark.notice.id']
238
-
239
- @benchmark.plaintext = HappyMapperTools::Benchmark::Plaintext.new
240
- @benchmark.plaintext.plaintext = @attribute['benchmark.plaintext']
241
- @benchmark.plaintext.id = @attribute['benchmark.plaintext.id']
242
-
243
- @benchmark.reference = HappyMapperTools::Benchmark::ReferenceBenchmark.new
244
- @benchmark.reference.href = @attribute['reference.href']
245
- @benchmark.reference.dc_publisher = @attribute['reference.dc.publisher']
246
- @benchmark.reference.dc_source = @attribute['reference.dc.source']
247
- end
248
-
249
- def populate_groups
250
- group_array = []
251
- @data['controls'].each do |control|
252
- group = HappyMapperTools::Benchmark::Group.new
253
- group.id = control['id']
254
- group.title = control['gtitle']
255
- group.description = "<GroupDescription>#{control['gdescription']}</GroupDescription>"
256
-
257
- group.rule = HappyMapperTools::Benchmark::Rule.new
258
- group.rule.id = control['rid']
259
- group.rule.severity = control['severity']
260
- group.rule.weight = control['rweight']
261
- group.rule.version = control['rversion']
262
- group.rule.title = control['title'].tr("\n", ' ')
263
- group.rule.description = "<VulnDiscussion>#{control['desc'].tr("\n", ' ')}</VulnDiscussion><FalsePositives></FalsePositives><FalseNegatives></FalseNegatives><Documentable>false</Documentable><Mitigations></Mitigations><SeverityOverrideGuidance></SeverityOverrideGuidance><PotentialImpacts></PotentialImpacts><ThirdPartyTools></ThirdPartyTools><MitigationControl></MitigationControl><Responsibility></Responsibility><IAControls></IAControls>"
264
-
265
- group.rule.reference = HappyMapperTools::Benchmark::ReferenceGroup.new
266
- group.rule.reference.dc_publisher = @attribute['reference.dc.publisher']
267
- group.rule.reference.dc_title = @attribute['reference.dc.title']
268
- group.rule.reference.dc_subject = @attribute['reference.dc.subject']
269
- group.rule.reference.dc_type = @attribute['reference.dc.type']
270
- group.rule.reference.dc_identifier = @attribute['reference.dc.identifier']
271
-
272
- group.rule.ident = HappyMapperTools::Benchmark::Ident.new
273
- group.rule.ident.system = 'https://public.cyber.mil/stigs/cci/'
274
- group.rule.ident.ident = control['cci']
275
-
276
- group.rule.fixtext = HappyMapperTools::Benchmark::Fixtext.new
277
- group.rule.fixtext.fixref = control['fixref']
278
- group.rule.fixtext.fixtext = control['fix']
279
-
280
- group.rule.fix = HappyMapperTools::Benchmark::Fix.new
281
- group.rule.fix.id = control['fixref']
282
-
283
- group.rule.check = HappyMapperTools::Benchmark::Check.new
284
- group.rule.check.system = control['checkref']
285
- group.rule.check.content_ref = HappyMapperTools::Benchmark::ContentRef.new
286
- group.rule.check.content_ref.name = @attribute['content_ref.name']
287
- group.rule.check.content_ref.href = @attribute['content_ref.href']
288
- group.rule.check.content = control['check']
289
-
290
- group_array << group
291
- end
292
- @benchmark.group = group_array
293
- end
294
-
295
223
  def generate_title(title, json, date)
296
224
  title ||= "Untitled - Checklist Created from Automated InSpec Results JSON; Profiles: #{json['profiles'].map { |x| x['name'] }.join(' | ')}"
297
225
  title + " Checklist Date: #{date || Date.today.to_s}"