inspec_tools 2.0.6 → 2.3.2

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: 43b88686ec67ec39b204d239fc5374c6448b9cfa1c1bd6b7832966b90619cc23
4
- data.tar.gz: dca55a3609c9ff90186d7e83f6017b4653b211b871e4ff2671f834e203a39879
3
+ metadata.gz: f1facda0eab24bdef7fafd9c8c92c5c94efedee3465bbf589b98fd4a61011be2
4
+ data.tar.gz: a38564b26ff610046007e427c8b45950b743087703224470b0513e94941d6cc7
5
5
  SHA512:
6
- metadata.gz: eab9120d563910e628f1dbfa47dc11b1abfff5992a040a23ad7978ae42567584a34dcc8bbfa0a926b3abfb55ed58f04f2f91a056f458ee75f9ca353745c3ebb5
7
- data.tar.gz: 17c1fea7003f96df5ce83fbda87165144b389ae6475521bb4e14af8eda259f2093f7a206ab7f9537994b645595fdcb4fbb3c3ece81aa283c4705e3c5d3553307
6
+ metadata.gz: 45b1990c5f4a2e5fa1fd21af344ac9cd9cdf5b60fce134ed80e7e4593a2c315ad09e994d5efc711fa0d6485f26899a5c35dec5e8ba0c37686cfaca9f483304da
7
+ data.tar.gz: 1022c5eae8dd7e6645e39fae3fbcb4babc195c2876d76d5f6496bb976a7515cb004d476176e19d70bba0507f8c8f1c355f48899c00fb39e79adcc2f26333fc08
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
@@ -100,13 +100,15 @@ If the specified threshold is not met, an error code (1) is returned along with
100
100
 
101
101
  The compliance score are rounded down to the nearest whole number. For example a score of 77.3 would be displayed as 77.
102
102
 
103
+ Thresholds provided inline (i.e. `-i`) override thresholds provided by files (i.e. `-f`).
104
+
103
105
  ```
104
106
  USAGE: inspec_tools compliance [OPTIONS] -j <inspec-json> -i <threshold-inline>
105
107
  inspec_tools compliance [OPTIONS] -j <inspec-json> -f <threshold-file>
106
108
  FLAGS:
107
109
  -j --inspec-json <inspec-json> : path to InSpec results Json
108
110
  -i --template-inline <threshold-inline> : inline compliance threshold definition
109
- -f --template-file <threshold-file> : yaml file with compliance threshold definition
111
+ -f --threshold-file <threshold-file> : yaml file with compliance threshold definition
110
112
  Examples:
111
113
 
112
114
  inspec_tools compliance -j examples/sample_json/rhel-simp.json -i '{compliance.min: 80, failed.critical.max: 0, failed.high.max: 0}'
@@ -135,11 +137,11 @@ failed.high.max: 1
135
137
 
136
138
  #### In-Line Examples
137
139
  ```
138
- {compliance: {min: 90}, failed: {critical: {max: 0}, high: {max: 0}}}
140
+ "{compliance: {min: 90}, failed: {critical: {max: 0}, high: {max: 0}}}"
139
141
  ```
140
142
 
141
143
  ```
142
- {compliance.min: 81, failed.critical.max: 0, failed.high.max: 0}
144
+ "{compliance.min: 81, failed.critical.max: 0, failed.high.max: 0}"
143
145
  ```
144
146
 
145
147
  ## summary
@@ -185,12 +187,16 @@ Using additional flags will override the normal output and only display the outp
185
187
 
186
188
  USAGE: inspec_tools summary [OPTIONS] -j <inspec-json>
187
189
 
190
+ Thresholds provided inline (i.e. `-i`) override thresholds provided by files (i.e. `-t`).
191
+
192
+
188
193
  ```
189
194
  FLAGS:
190
- -j --inspec-json <inspec-json> : path to InSpec results JSON
191
- -V --verbose, --no-verbose : print verbose an debug output
192
- -f --json-full, --no-json-full : print the summary STDOUT as JSON
193
- -k --json-counts, --no-json_cou : print the reslut status to STDOUT as JSON
195
+ -j --inspec-json <inspec-json> : path to InSpec results JSON
196
+ -f --json-full, --no-json-full : print the summary STDOUT as JSON
197
+ -k --json-counts, --no-json-counts : print the result status to STDOUT as JSON
198
+ -t, --threshold-file=THRESHOLD_FILE] : path to threshold YAML file
199
+ -i, --threshold-inline=THRESHOLD_INLINE] : string of text representing threshold YAML inline
194
200
 
195
201
  Examples:
196
202
 
@@ -220,16 +226,18 @@ example: inspec_tools xccdf2inspec -x xccdf_file.xml -a attributes.yml -o myprof
220
226
 
221
227
  `inspec2xccdf` converts an InSpec profile in json format to a STIG XCCDF Document
222
228
 
229
+ See [examples documentation](./examples/inspec2xccdf/README.md) for additional guidance on usage including attribute details.
230
+
223
231
  ```
224
232
  USAGE: inspec_tools inspec2xccdf [OPTIONS] -j <inspec-json> -a <xccdf-attr-yml> -o <xccdf-xml>
225
233
 
226
234
  FLAGS:
227
235
  -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]
236
+ -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.
237
+ -o --output <xccdf-xml> : name or path to create the XCCDF and title to give the XCCDF
238
+ -m, [--metadata=METADATA] : path to json file with additional host metadata for the XCCDF file
231
239
 
232
- example: inspec_tools inspec2xccdf -j example.json -a attributes.yml -o xccdf.xml
240
+ example: inspec_tools inspec2xccdf -j examples/sample_json/good_nginxresults.json -a lib/data/attributes.yml -o output.xccdf
233
241
  ```
234
242
 
235
243
  ## csv2inspec
@@ -295,7 +303,7 @@ FLAGS:
295
303
  -s --separate-files [true | false] : output the resulting controls as multiple files (default: true) [optional]
296
304
  -d --debug : debug run [optional]
297
305
 
298
- example: inspec_tools pdf2inspec -p benchmark.pdf -o /path/to/myprofile -f ruby -s true
306
+ example: inspec_tools pdf2inspec -p examples/CIS_Ubuntu_Linux_16.04_LTS_Benchmark_v1.0.0.pdf -o /path/to/myprofile -f ruby -s true
299
307
  ```
300
308
 
301
309
  ## xlsx2inspec
data/Rakefile CHANGED
@@ -1,10 +1,9 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'rake/testtask'
3
2
  require File.expand_path('../lib/inspec_tools/version', __FILE__)
4
3
 
5
4
  Rake::TestTask.new(:test) do |t|
6
- t.libs << "test"
7
- t.libs << "lib"
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
8
7
  t.test_files = FileList['test/**/*_test.rb']
9
8
  end
10
9
 
@@ -20,11 +19,86 @@ namespace :test do
20
19
  'test/unit/inspec_tools_test.rb'
21
20
  ])
22
21
  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
31
+
32
+ desc 'Build for release'
33
+ task :build_release do
34
+
35
+ Rake::Task["generate_mapping_objects"].reenable
36
+ Rake::Task["generate_mapping_objects"].invoke
37
+
38
+ system('gem build inspec_tools.gemspec')
39
+ end
40
+
41
+ desc 'Generate mapping objects'
42
+ task :generate_mapping_objects do
43
+ require 'roo'
44
+
45
+ nist_mapping_cis_controls = ENV['NIST_MAPPING_CIS_CONTROLS'] || 'NIST_Map_02052020_CIS_Controls_Version_7.1_Implementation_Groups_1.2.xlsx'.freeze
46
+ nist_mapping_cis_critical_controls = ENV['NIST_MAPPING_CIS_CRITICAL_CONTROLS'] || 'NIST_Map_09212017B_CSC-CIS_Critical_Security_Controls_VER_6.1_Excel_9.1.2016.xlsx'.freeze
47
+
48
+ data_root_path = File.join(File.expand_path(__dir__), 'lib', 'data')
49
+ cis_controls_path = File.join(data_root_path, nist_mapping_cis_controls)
50
+ cis_critical_controls_path = File.join(data_root_path, nist_mapping_cis_critical_controls)
51
+
52
+ raise "#{cis_controls_path} does not exist" unless File.exist?(cis_controls_path)
53
+
54
+ raise "#{cis_critical_controls_path} does not exist" unless File.exist?(cis_critical_controls_path)
55
+
56
+ marshal_cis_controls(cis_controls_path, data_root_path)
57
+ marshal_cis_critical_controls(cis_critical_controls_path, data_root_path)
23
58
  end
24
59
 
25
- desc 'Build and publish the gem'
26
- task publish: :build do
27
- system("gem push pkg/inspec_tools-#{InspecTools::VERSION}.gem")
60
+ def marshal_cis_controls(cis_controls_path, data_root_path)
61
+ cis_to_nist = {}
62
+ Roo::Spreadsheet.open(cis_controls_path).sheet(3).each do |row|
63
+ if row[3].is_a?(Numeric)
64
+ cis_to_nist[row[3].to_s] = row[0]
65
+ else
66
+ cis_to_nist[row[2].to_s] = row[0] unless (row[2] == '') || row[2].to_i.nil?
67
+ end
68
+ end
69
+ output_file = File.new(File.join(data_root_path, 'cis_to_nist_mapping'), 'w')
70
+ Marshal.dump(cis_to_nist, output_file)
71
+ output_file.close
28
72
  end
29
73
 
30
- task :default => :test
74
+ def marshal_cis_critical_controls(cis_critical_controls_path, data_root_path)
75
+ controls_spreadsheet = Roo::Spreadsheet.open(cis_critical_controls_path)
76
+ controls_spreadsheet.default_sheet = 'VER 6.1 Controls'
77
+ headings = {}
78
+ controls_spreadsheet.row(3).each_with_index { |header, idx| headings[header] = idx }
79
+
80
+ nist_ver = 4
81
+ cis_ver = controls_spreadsheet.row(2)[4].split(' ')[-1]
82
+ control_count = 1
83
+ mapping = []
84
+ ((controls_spreadsheet.first_row + 3)..controls_spreadsheet.last_row).each do |row_value|
85
+ current_row = {}
86
+ if controls_spreadsheet.row(row_value)[headings['NIST SP 800-53 Control #']].to_s != ''
87
+ current_row[:nist] = controls_spreadsheet.row(row_value)[headings['NIST SP 800-53 Control #']].to_s
88
+ else
89
+ current_row[:nist] = 'Not Mapped'
90
+ end
91
+ current_row[:nist_ver] = nist_ver
92
+ if controls_spreadsheet.row(row_value)[headings['Control']].to_s == ''
93
+ current_row[:cis] = control_count.to_s
94
+ control_count += 1
95
+ else
96
+ current_row[:cis] = controls_spreadsheet.row(row_value)[headings['Control']].to_s
97
+ end
98
+ current_row[:cis_ver] = cis_ver
99
+ mapping << current_row
100
+ end
101
+ output_file = File.new(File.join(data_root_path, 'cis_to_nist_critical_controls'), 'w')
102
+ Marshal.dump(mapping, output_file)
103
+ output_file.close
104
+ end
@@ -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
- def to_inspec
24
+ def to_inspec(control_name_prefix: nil)
33
25
  @controls = []
34
- @cci_xml = nil
35
26
  @profile = {}
36
- read_cci_xml
37
- insert_json_metadata
38
- parse_controls
27
+ @cci_xml = Utils::CciXml.get_cci_list('U_CCI_List.xml')
28
+ insert_metadata
29
+ parse_controls(control_name_prefix)
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
78
59
  end
79
60
 
80
- def parse_controls
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
67
+ end
68
+
69
+ def parse_controls(prefix)
81
70
  @csv.each do |row|
82
- print '.'
83
71
  control = {}
84
- control['id'] = row[@mapping['control.id']] unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
85
- control['title'] = row[@mapping['control.title']] unless @mapping['control.title'].nil? || row[@mapping['control.title']].nil?
86
- control['desc'] = row[@mapping['control.desc']] unless @mapping['control.desc'].nil? || row[@mapping['control.desc']].nil?
72
+ control['id'] = generate_control_id(prefix, row[@mapping['control.id']]) unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
73
+ control['title'] = row[@mapping['control.title']] unless @mapping['control.title'].nil? || row[@mapping['control.title']].nil?
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']])
@@ -97,5 +90,15 @@ module InspecTools
97
90
  @controls << control
98
91
  end
99
92
  end
93
+
94
+ def generate_control_id(prefix, id)
95
+ return id if prefix.nil?
96
+
97
+ "#{prefix}-#{id}"
98
+ end
100
99
  end
101
100
  end
101
+
102
+ # rubocop:enable Metrics/AbcSize
103
+ # rubocop:enable Metrics/PerceivedComplexity
104
+ # rubocop:enable Metrics/CyclomaticComplexity
@@ -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