inspec_delta 0.1.1
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 +7 -0
- data/README.md +29 -0
- data/bin/inspec_delta +4 -0
- data/lib/inspec_delta.rb +10 -0
- data/lib/inspec_delta/command.rb +21 -0
- data/lib/inspec_delta/commands/profile.rb +23 -0
- data/lib/inspec_delta/objects.rb +4 -0
- data/lib/inspec_delta/objects/control.rb +382 -0
- data/lib/inspec_delta/objects/profile.rb +69 -0
- data/lib/inspec_delta/parsers.rb +3 -0
- data/lib/inspec_delta/parsers/benchmark.rb +78 -0
- data/lib/inspec_delta/version.rb +9 -0
- metadata +198 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e32fef4ea8661cbb7510dfa92da7367237712a4d1e6c4ee709855d94359b8ce6
|
4
|
+
data.tar.gz: f9dc68c827755e37e74a0da42e90e2235475d4edaabcaf37df2760624dba7a55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 574ada892f56ddbfd1dec143c8bd1438ae45330355c9e84e050f648eb599c3598fe715ca3888ac679bd8c05b21c77bbc80b37059e090ecae605dd47397c2df87
|
7
|
+
data.tar.gz: d9b76544b91050d1ae6f4486f7fd79bac205e411f5ff8b39b071edc87b6760613595562c1fc76fa7f028d7d911d4ad52907b69d78f7920b450df0908e6a6b3aa
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
[](http://badge.fury.io/rb/inspec_delta)
|
2
|
+
[](https://github.com/jrperron88/inspec_delta/actions?query=workflow%3ACI+branch%3AETSOE-2220-TravisCI)
|
3
|
+
|
4
|
+
# Inspec Delta
|
5
|
+
|
6
|
+
This Gem aims to make the maintenance of Inspec profiles representing security benchmarks less of a burden by providing helpful command line tools.
|
7
|
+
|
8
|
+
For information on STIGs visit the [DoD Cyber Exchange](https://public.cyber.mil/stigs/).
|
9
|
+
|
10
|
+
For stig definition files visit the [DoD Cyber Exchange Document Library](https://public.cyber.mil/stigs/downloads/).
|
11
|
+
|
12
|
+
## Commands
|
13
|
+
|
14
|
+
### inspec_delta help
|
15
|
+
- This will list all the commands along with their descriptions.
|
16
|
+
|
17
|
+
### inspec_delta profile update
|
18
|
+
This command updates an existing Inspec profile for a STIG with the metadata from a new revision to that STIG.
|
19
|
+
It reads through the XCCDF XML file from the new revision and updates the existing profile's controls as needed.
|
20
|
+
|
21
|
+
#### Usage
|
22
|
+
> inspec_delta profile update -p /path/to/profile -s /path/to/stig.xml
|
23
|
+
|
24
|
+
#### Options
|
25
|
+
```ruby
|
26
|
+
|
27
|
+
-p, --pr, [--profile_path=PROFILE_PATH] # Specified path to the root directory of the existing Inspec profile.
|
28
|
+
-s, --st, [--stig_file_path=STIG_PATH] # Specified path to the XCCDF XML file from the new STIG revision we are updating to.
|
29
|
+
```
|
data/bin/inspec_delta
ADDED
data/lib/inspec_delta.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'commands/profile'
|
4
|
+
|
5
|
+
module InspecDelta
|
6
|
+
# Public: Various commands issued for the user to interact with. These will
|
7
|
+
# be shown when they run inspec_delta.
|
8
|
+
class Command < Thor
|
9
|
+
# This will cause the return value of inspec_delta to be non-zero when an execution failure occurs.
|
10
|
+
# It is necessary to define this to suprress warning messages from Thor. They plan on making
|
11
|
+
# the 2.0 release of Thor remove the need to define this.
|
12
|
+
# GitHub Issue: https://github.com/erikhuda/thor/issues/244
|
13
|
+
def self.exit_on_failure?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Contains subcommands for working with Inspec Profiles
|
18
|
+
desc 'profile', 'Subcommands: update'
|
19
|
+
subcommand 'profile', InspecDelta::Commands::Profile
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module InspecDelta
|
4
|
+
module Commands
|
5
|
+
# This class will take care of operations related to profile manipulation
|
6
|
+
class Profile < Thor
|
7
|
+
desc 'update', 'Update profile from STIG file'
|
8
|
+
method_option :profile_path,
|
9
|
+
aliases: %w[-p --pr],
|
10
|
+
desc: 'The path to the directory that contains the profile to modify.',
|
11
|
+
required: true
|
12
|
+
method_option :stig_file_path,
|
13
|
+
aliases: %w[-s --st],
|
14
|
+
desc: 'The path to the stig file to apply to the profile.',
|
15
|
+
required: true
|
16
|
+
def update
|
17
|
+
prof = InspecDelta::Object::Profile.new(options[:profile_path])
|
18
|
+
prof.update(options[:stig_file_path])
|
19
|
+
prof.format
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'facets/string'
|
4
|
+
require 'inspec-objects'
|
5
|
+
require 'ruby_parser'
|
6
|
+
require 'ruby2ruby'
|
7
|
+
|
8
|
+
module InspecDelta
|
9
|
+
##
|
10
|
+
# This module represents the objects used in the InspecDelta module
|
11
|
+
module Object
|
12
|
+
##
|
13
|
+
# This class represents a modified representation of an Inspec Control
|
14
|
+
class Control < Inspec::Object::Control
|
15
|
+
MAX_LINE_LENGTH = 120
|
16
|
+
WORD_WRAP_INDENT = 4
|
17
|
+
|
18
|
+
attr_accessor :global_code, :control_code
|
19
|
+
attr_reader :control_string
|
20
|
+
|
21
|
+
##
|
22
|
+
# Creates a new Control based on the Inspec Object Control definition
|
23
|
+
def initialize
|
24
|
+
super
|
25
|
+
|
26
|
+
@global_code = []
|
27
|
+
@control_code = []
|
28
|
+
@control_string = ''
|
29
|
+
end
|
30
|
+
|
31
|
+
# Updates a string representation of a Control with string substitutions
|
32
|
+
#
|
33
|
+
# @param [Control] other - The Control to be merged in
|
34
|
+
#
|
35
|
+
# @return [control_string] String updated string with the changes from other
|
36
|
+
def apply_updates(other)
|
37
|
+
apply_updates_title(other.title)
|
38
|
+
apply_updates_desc(other.descriptions[:default])
|
39
|
+
apply_updates_impact(other.impact)
|
40
|
+
apply_updates_tags(other)
|
41
|
+
@control_string
|
42
|
+
end
|
43
|
+
|
44
|
+
# Updates a string representation of a Control's title with string substitutions
|
45
|
+
#
|
46
|
+
# @param [String] title - The title to be applied to the Control
|
47
|
+
def apply_updates_title(title)
|
48
|
+
return if title.to_s.empty?
|
49
|
+
|
50
|
+
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
|
51
|
+
|
52
|
+
@control_string.sub!(
|
53
|
+
/title\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
|
54
|
+
"title %q{#{title}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Updates a string representation of a Control's description with string substitutions
|
59
|
+
#
|
60
|
+
# @param [Control] desc - The description to be applied to the Control
|
61
|
+
def apply_updates_desc(desc)
|
62
|
+
return if desc.to_s.empty?
|
63
|
+
|
64
|
+
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
|
65
|
+
|
66
|
+
@control_string.sub!(
|
67
|
+
/desc\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
|
68
|
+
"desc %q{#{desc}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Updates a string representation of a Control's impact with string substitutions
|
73
|
+
#
|
74
|
+
# @param [Decimal] impact - The impact to be applied to the Control
|
75
|
+
def apply_updates_impact(impact)
|
76
|
+
return if impact.nil?
|
77
|
+
|
78
|
+
@control_string.sub!(/impact\s+\d\.\d/, "impact #{impact}")
|
79
|
+
end
|
80
|
+
|
81
|
+
# Updates a string representation of a Control's tags with string substitutions
|
82
|
+
#
|
83
|
+
# @param [Control] other - The Control to be merged in
|
84
|
+
def apply_updates_tags(other)
|
85
|
+
other.tags.each do |ot|
|
86
|
+
tag = @tags.detect { |t| t.key == ot.key }
|
87
|
+
next unless tag
|
88
|
+
|
89
|
+
if ot.value.instance_of?(String)
|
90
|
+
apply_updates_tags_string(ot)
|
91
|
+
elsif ot.value.instance_of?(Array)
|
92
|
+
apply_updates_tags_array(ot)
|
93
|
+
elsif ot.value.instance_of?(FalseClass) || ot.value.instance_of?(TrueClass)
|
94
|
+
apply_updates_tags_bool(ot)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Updates a string representation of a Control's tags with string substitutions
|
100
|
+
#
|
101
|
+
# @param [Tag] ot - The Tag to be merged in
|
102
|
+
def apply_updates_tags_string(tag)
|
103
|
+
if tag.value.empty?
|
104
|
+
@control_string.sub!(
|
105
|
+
/tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
|
106
|
+
"tag '#{tag.key}': nil\n"
|
107
|
+
)
|
108
|
+
else
|
109
|
+
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
|
110
|
+
|
111
|
+
@control_string.sub!(
|
112
|
+
/tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
|
113
|
+
"tag '#{tag.key}': %q{#{tag.value}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Updates a string representation of a Control's tags with string substitutions
|
119
|
+
#
|
120
|
+
# @param [Tag] ot - The Tag to be merged in
|
121
|
+
def apply_updates_tags_array(tag)
|
122
|
+
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
|
123
|
+
|
124
|
+
@control_string.sub!(
|
125
|
+
/tag\s+['"]?#{tag.key}['"]?:\s+(((%w\().*?(?<!\\)(\)))|((\[).*?(?<!\\)(\]))|(nil))\n/m,
|
126
|
+
"tag '#{tag.key}': #{tag.value}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Updates a string representation of a Control's tags with string substitutions
|
131
|
+
#
|
132
|
+
# @param [Tag] ot - The Tag to be merged in
|
133
|
+
def apply_updates_tags_bool(tag)
|
134
|
+
@control_string.sub!(
|
135
|
+
/tag\s+['"]?#{tag.key}['"]?:\s+(true|false|'')\n/,
|
136
|
+
"tag '#{tag.key}': #{tag.value}\n"
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Hash of tags we want to read from the benchmark
|
141
|
+
#
|
142
|
+
# @return [hash] benchmark_tags
|
143
|
+
def self.benchmark_tags
|
144
|
+
{
|
145
|
+
'severity' => :severity,
|
146
|
+
'gtitle' => :gtitle,
|
147
|
+
'satisfies' => :satisfies,
|
148
|
+
'gid' => :gid,
|
149
|
+
'rid' => :rid,
|
150
|
+
'stig_id' => :stig_id,
|
151
|
+
'fix_id' => :fix_id,
|
152
|
+
'cci' => :cci,
|
153
|
+
'false_negatives' => :false_negatives,
|
154
|
+
'false_positives' => :false_positives,
|
155
|
+
'documentable' => :documentable,
|
156
|
+
'mitigations' => :mitigations,
|
157
|
+
'severity_override_guidance' => :severity_override_guidance,
|
158
|
+
'potential_impacts' => :potential_impacts,
|
159
|
+
'third_party_tools' => :third_party_tools,
|
160
|
+
'mitigation_controls' => :mitigation_controls,
|
161
|
+
'responsibility' => :responsibility,
|
162
|
+
'ia_controls' => :ia_controls,
|
163
|
+
'check' => :check,
|
164
|
+
'fix' => :fix
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
# Creates a new Control from a benchmark hash definition
|
169
|
+
#
|
170
|
+
# @param [Hash] benchmark - Hash representation of a benchmark
|
171
|
+
#
|
172
|
+
# @return [Control] Control representation of the benchmark
|
173
|
+
def self.from_benchmark(benchmark)
|
174
|
+
control = new
|
175
|
+
control.descriptions[:default] = benchmark[:desc]
|
176
|
+
control.id = benchmark[:id]
|
177
|
+
control.title = benchmark[:title]
|
178
|
+
control.impact = impact(benchmark[:severity])
|
179
|
+
benchmark_tags.each do |tag, benchmark_key|
|
180
|
+
control.add_tag(Inspec::Object::Tag.new(tag, benchmark[benchmark_key]))
|
181
|
+
end
|
182
|
+
control
|
183
|
+
end
|
184
|
+
|
185
|
+
# Creates a new Control from a ruby definition
|
186
|
+
#
|
187
|
+
# @param [String] ruby_control_path - path to the ruby file that contains the control
|
188
|
+
#
|
189
|
+
# @return [Control] Control parsed from file
|
190
|
+
def self.from_ruby(ruby_control_path)
|
191
|
+
control = new
|
192
|
+
control_file_string = File.read(File.expand_path(ruby_control_path))
|
193
|
+
send(:parse_ruby, control, RubyParser.new.parse(control_file_string))
|
194
|
+
control.instance_variable_set(:@control_string, control_file_string)
|
195
|
+
control
|
196
|
+
end
|
197
|
+
|
198
|
+
# Converts the string severity into a decimal representation
|
199
|
+
#
|
200
|
+
# @param [String] severity - string representation of the severity
|
201
|
+
#
|
202
|
+
# @return [decimal] numerical representation of the severity if match is found
|
203
|
+
# @return [string] severity if no match is found
|
204
|
+
def self.impact(severity)
|
205
|
+
{
|
206
|
+
'low' => 0.3,
|
207
|
+
'medium' => 0.5,
|
208
|
+
'high' => 0.7
|
209
|
+
}.fetch(severity) { severity }
|
210
|
+
end
|
211
|
+
|
212
|
+
# Merges another controll into self
|
213
|
+
#
|
214
|
+
# Currently we are only mergings objects from the base Inspec::Objects::Control
|
215
|
+
# as that is what will be used for updated STIG merges
|
216
|
+
# id is not updated as that should be the unique identifier
|
217
|
+
#
|
218
|
+
# @param [Control] other - another control to take values from.
|
219
|
+
#
|
220
|
+
# @return [Control] self - updated with values from other control
|
221
|
+
def merge_from(other)
|
222
|
+
@title = other.title unless other.title.to_s.empty?
|
223
|
+
@descriptions[:default] = other.descriptions[:default] unless other.descriptions[:default].to_s.empty?
|
224
|
+
@impact = other.impact unless other.impact.nil?
|
225
|
+
other.tags.each do |ot|
|
226
|
+
tag = @tags.detect { |t| t.key == ot.key }
|
227
|
+
tag ? tag.value = ot.value : @tags.push(ot)
|
228
|
+
end
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
# Takes an Sexp that is an S-exp representation of a ruby control, and parse it into a control
|
233
|
+
#
|
234
|
+
# @param [Control] control - A Control that we want to load the ruby Sexp into
|
235
|
+
# @param [Sexp] ruby_object - An Sexp representation of a ruby object
|
236
|
+
# <syntax>:
|
237
|
+
# s(:iter,
|
238
|
+
# s(:call, nil, :control, s(:str, <controlId>)),
|
239
|
+
# s(:block,
|
240
|
+
# s(:call, nil, :title, s(:str, <title>)),
|
241
|
+
# s(:call, nil, :desc, s(:str, <desc>)),
|
242
|
+
# s(:call, nil, :impact, s(:lit, <impact>)),
|
243
|
+
# [<tag>,<RubyCode>](see <Tag> definition)
|
244
|
+
# ))
|
245
|
+
#
|
246
|
+
# <Tag>:
|
247
|
+
# s(:call, nil, :tag,
|
248
|
+
# s(:hash,
|
249
|
+
# s(:lit, :gtitle),
|
250
|
+
# <string> || <array_of_strings> || <comment_hash>))
|
251
|
+
# <string>:
|
252
|
+
# s(:str, 'str1')
|
253
|
+
# <array_of_strings>:
|
254
|
+
# s(:array,
|
255
|
+
# s(:str, 'str1'),
|
256
|
+
# s(:str, 'str2')
|
257
|
+
# <comment_hash>
|
258
|
+
# s(:hash,
|
259
|
+
# s(:lit, :"Regular Expression Usage"),
|
260
|
+
# s(:str, "RegEx Definition"))
|
261
|
+
#
|
262
|
+
# @return [Control] control - A Control that has been filled from the ruby_object
|
263
|
+
private_class_method def self.parse_base_control(control, ruby_object)
|
264
|
+
case ruby_object[0]
|
265
|
+
when :call
|
266
|
+
send(:parse_base_control_call, control, ruby_object)
|
267
|
+
when :block
|
268
|
+
ruby_object.each { |x| send(:parse_base_control, control, x) }
|
269
|
+
when :iter
|
270
|
+
if ruby_object[1][2] == :control
|
271
|
+
control.id = ruby_object[1][3][1]
|
272
|
+
send(:parse_base_control, control, ruby_object[3])
|
273
|
+
else
|
274
|
+
control.control_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
|
275
|
+
end
|
276
|
+
else
|
277
|
+
control.control_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone)) if ruby_object.instance_of?(Sexp)
|
278
|
+
end
|
279
|
+
control
|
280
|
+
end
|
281
|
+
|
282
|
+
# Takes an Sexp that is an S-exp representation of a ruby control with a type of call, and parse it into a control
|
283
|
+
#
|
284
|
+
# @param [Control] control - A Control that we want to load the ruby Sexp into
|
285
|
+
# @param [Sexp] ruby_object - An Sexp representation of a ruby object with a call type
|
286
|
+
# <syntax> (multiple examples):
|
287
|
+
# s(:call, nil, :control, s(:str, <title>))
|
288
|
+
# s(:call, nil, :title, s(:str, <title>))
|
289
|
+
# s(:call, nil, :desc, s(:str, <desc>))
|
290
|
+
# s(:call, nil, :impact, s(:lit, <impact>))
|
291
|
+
# <tag> (see <Tag> parameter)
|
292
|
+
# <RubyCode>
|
293
|
+
#
|
294
|
+
# <Tag>:
|
295
|
+
# s(:call, nil, :tag,
|
296
|
+
# s(:hash,
|
297
|
+
# s(:lit, :gtitle),
|
298
|
+
# <string> || <array_of_strings> || <comment_hash>))
|
299
|
+
# <string>:
|
300
|
+
# s(:str, 'str1')
|
301
|
+
# <array_of_strings>:
|
302
|
+
# s(:array,
|
303
|
+
# s(:str, 'str1'),
|
304
|
+
# s(:str, 'str2')
|
305
|
+
# <comment_hash>
|
306
|
+
# s(:hash,
|
307
|
+
# s(:lit, :"Regular Expression Usage"),
|
308
|
+
# s(:str, "RegEx Definition"))
|
309
|
+
#
|
310
|
+
# @return [Control] control - A Control that has been filled from the ruby_object
|
311
|
+
private_class_method def self.parse_base_control_call(control, ruby_object)
|
312
|
+
case ruby_object[2]
|
313
|
+
when :control
|
314
|
+
control.id = ruby_object[3][1]
|
315
|
+
when :title
|
316
|
+
control.title = ruby_object[3][1]
|
317
|
+
when :desc
|
318
|
+
control.descriptions[:default] = ruby_object[3][1]
|
319
|
+
when :impact
|
320
|
+
control.impact = ruby_object[3][1]
|
321
|
+
when :tag
|
322
|
+
control.add_tag(
|
323
|
+
Inspec::Object::Tag.new(ruby_object[3][1][1].to_s, Ruby2Ruby.new.process(ruby_object[3][2].deep_clone))
|
324
|
+
)
|
325
|
+
else
|
326
|
+
control.control_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
|
327
|
+
end
|
328
|
+
control
|
329
|
+
end
|
330
|
+
|
331
|
+
# Takes an Sexp that is an S-exp representation of a customized ruby control, and parse it into a control
|
332
|
+
#
|
333
|
+
# @param [Control] control - A Control that we want to load the ruby Sexp into
|
334
|
+
# @param [Sexp] ruby_object - An Sexp representation of a ruby object
|
335
|
+
# <syntax>:
|
336
|
+
# s(:block,
|
337
|
+
# s([<RubyCode>[0-9],<Control>,<RubyCode>[0-9]]))
|
338
|
+
#
|
339
|
+
# @return [Control] control - A Control that has been filled from the ruby_object
|
340
|
+
private_class_method def self.parse_ruby(control, ruby_object)
|
341
|
+
case ruby_object[0]
|
342
|
+
when :call
|
343
|
+
control.global_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
|
344
|
+
when :block
|
345
|
+
ruby_object.each { |x| send(:parse_ruby, control, x) }
|
346
|
+
when :iter
|
347
|
+
case ruby_object[1][2]
|
348
|
+
when :control
|
349
|
+
send(:parse_base_control, control, ruby_object)
|
350
|
+
else
|
351
|
+
control.global_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
|
352
|
+
end
|
353
|
+
else
|
354
|
+
# Anything that doesn't fall into the other scenarios is pure ruby code that falls outside of the control
|
355
|
+
# we want to add this code to the global code to be preserved as code.
|
356
|
+
control.global_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone)) if ruby_object.instance_of?(Sexp)
|
357
|
+
end
|
358
|
+
control
|
359
|
+
end
|
360
|
+
|
361
|
+
# Converts the Control object into a string representation of a Ruby Object
|
362
|
+
#
|
363
|
+
# unable to use the super function as it is already converted to a string
|
364
|
+
#
|
365
|
+
# @return [string] the control as a ruby string
|
366
|
+
def to_ruby
|
367
|
+
res = []
|
368
|
+
res.push global_code unless global_code.empty?
|
369
|
+
res.push "control #{id.inspect} do"
|
370
|
+
res.push " title #{title.inspect}" unless title.to_s.empty?
|
371
|
+
res.push " desc #{descriptions[:default].inspect}" unless descriptions[:default].to_s.empty?
|
372
|
+
res.push " impact #{impact}" unless impact.nil?
|
373
|
+
tags.each do |t|
|
374
|
+
res.push(" #{t.to_ruby}")
|
375
|
+
end
|
376
|
+
res.push control_code unless control_code.empty?
|
377
|
+
res.push 'end'
|
378
|
+
res.join("\n")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module InspecDelta
|
4
|
+
##
|
5
|
+
# This module represents the objects used in the InspecDelta module
|
6
|
+
module Object
|
7
|
+
# This class will take care of operations related to profile manipulation
|
8
|
+
class Profile
|
9
|
+
# Internal: Initializes the Profile object upon instantiation
|
10
|
+
#
|
11
|
+
# profile_path: String, path to the inspec profile's root directory.
|
12
|
+
# _options: Unused, contains extraneous parameters that may be passed to the initializer
|
13
|
+
#
|
14
|
+
# Returns: Nothing
|
15
|
+
def initialize(profile_path)
|
16
|
+
@profile_path = profile_path
|
17
|
+
raise StandardError, "Profile directory at #{@profile_path} not found" unless Dir.exist?(@profile_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Formats a the ruby controls using rubocop with the rubo config file in the profile
|
21
|
+
#
|
22
|
+
def format
|
23
|
+
control_dir = File.join(File.expand_path(@profile_path), 'controls')
|
24
|
+
rubo_file = File.join(File.expand_path(@profile_path), '.rubocop.yml')
|
25
|
+
raise StandardError, "Rubocop configuration file at #{rubo_file} not found" unless File.exist?(rubo_file)
|
26
|
+
|
27
|
+
`rubocop -a #{control_dir} -c #{rubo_file}`
|
28
|
+
end
|
29
|
+
|
30
|
+
# Updates a profile metadata with definitions from a STIG xml file
|
31
|
+
#
|
32
|
+
# @param [profile_path] String - path to the inspec profile's root directory.
|
33
|
+
# @param [stig_file_path] String - The STIG file to be applied to profile.
|
34
|
+
def update(stig_file_path)
|
35
|
+
raise StandardError, "STIG file at #{stig_file_path} not found" unless File.exist?(stig_file_path)
|
36
|
+
|
37
|
+
control_dir = "#{@profile_path}/controls"
|
38
|
+
benchmark = InspecDelta::Parser::Benchmark.get_benchmark(stig_file_path)
|
39
|
+
benchmark.each do |control_id, control|
|
40
|
+
benchmark_control = InspecDelta::Object::Control.from_benchmark(control)
|
41
|
+
profile_control_path = File.join(File.expand_path(control_dir), "#{control_id}.rb")
|
42
|
+
if File.file?(profile_control_path)
|
43
|
+
update_existing_control_file(profile_control_path, benchmark_control)
|
44
|
+
else
|
45
|
+
create_new_control_file(profile_control_path, benchmark_control)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Updates a control file with the updates from the stig
|
51
|
+
#
|
52
|
+
# @param [profile_control_path] String - The location of the Inspec profile on disk
|
53
|
+
# @param [benchmark_control] Control - Control built from the Inspec Benchmark
|
54
|
+
def update_existing_control_file(profile_control_path, benchmark_control)
|
55
|
+
profile_control = InspecDelta::Object::Control.from_ruby(profile_control_path)
|
56
|
+
updated_control = profile_control.apply_updates(benchmark_control)
|
57
|
+
File.open(profile_control_path, 'w') { |f| f.puts updated_control }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Creates a control file with the string representation of the benchmark control
|
61
|
+
#
|
62
|
+
# @param [profile_control_path] String - The location of the Inspec profile on disk
|
63
|
+
# @param [benchmark_control] Control - Control built from the Inspec Benchmark
|
64
|
+
def create_new_control_file(profile_control_path, benchmark_control)
|
65
|
+
File.open(profile_control_path, 'w') { |f| f.puts benchmark_control.to_ruby }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'happy_mapper_tools/stig_attributes'
|
4
|
+
|
5
|
+
module InspecDelta
|
6
|
+
##
|
7
|
+
# This module represents the objects used in the InspecDelta module
|
8
|
+
module Parser
|
9
|
+
##
|
10
|
+
# This class represents a modified representation of an STIG benchmark
|
11
|
+
# which is a collection of Inspec Controls
|
12
|
+
class Benchmark
|
13
|
+
# Creates a new Benchmark from a STIG file
|
14
|
+
#
|
15
|
+
# @param [File] file - the STIG xml file
|
16
|
+
#
|
17
|
+
# @return [Hash] Hash representation of the benchmark - collection of Controls
|
18
|
+
def self.get_benchmark(file)
|
19
|
+
benchmark = HappyMapperTools::StigAttributes::Benchmark.parse(File.read(file))
|
20
|
+
benchmark_title = "#{benchmark.title} :: Version #{benchmark.version}, #{benchmark.plaintext&.plaintext}"
|
21
|
+
|
22
|
+
mapped_benchmark_group = benchmark.group.map do |b| # rubocop:disable Metrics/BlockLength
|
23
|
+
g = {}
|
24
|
+
|
25
|
+
g[:stig_title] = benchmark_title
|
26
|
+
|
27
|
+
g[:id] = b.id
|
28
|
+
g[:gtitle] = b.title
|
29
|
+
g[:description] = b.description
|
30
|
+
g[:gid] = b.id
|
31
|
+
|
32
|
+
rule = b.rule
|
33
|
+
g[:rid] = rule.id
|
34
|
+
g[:severity] = rule.severity
|
35
|
+
g[:stig_id] = rule.version
|
36
|
+
g[:title] = rule.title
|
37
|
+
|
38
|
+
description = rule.description.details
|
39
|
+
discussion = description.vuln_discussion
|
40
|
+
g[:vuln_discussion] = discussion
|
41
|
+
g[:false_negatives] = description.false_negatives
|
42
|
+
g[:false_positives] = description.false_positives
|
43
|
+
g[:documentable] = description.documentable
|
44
|
+
g[:mitigations] = description.mitigations
|
45
|
+
g[:severity_override_guidance] = description.severity_override_guidance
|
46
|
+
g[:potential_impacts] = description.potential_impacts
|
47
|
+
g[:third_party_tools] = description.third_party_tools
|
48
|
+
g[:mitigation_controls] = description.mitigation_controls
|
49
|
+
g[:responsibility] = description.responsibility
|
50
|
+
g[:ia_controls] = description.ia_controls
|
51
|
+
g[:desc] = discussion.split('Satisfies: ')[0].strip
|
52
|
+
if discussion.split('Satisfies: ').length > 1
|
53
|
+
g[:satisfies] = discussion.split('Satisfies: ')[1].split(',').map(&:strip)
|
54
|
+
end
|
55
|
+
|
56
|
+
reference_group = rule.reference
|
57
|
+
g[:dc_identifier] = reference_group.dc_identifier
|
58
|
+
g[:dc_publisher] = reference_group.dc_publisher
|
59
|
+
g[:dc_source] = reference_group.dc_source
|
60
|
+
g[:dc_subject] = reference_group.dc_subject
|
61
|
+
g[:dc_title] = reference_group.dc_title
|
62
|
+
g[:dc_type] = reference_group.dc_type
|
63
|
+
|
64
|
+
g[:cci] = rule.idents.select { |t| t.start_with?('CCI-') } # XCCDFReader
|
65
|
+
|
66
|
+
g[:fix] = rule.fixtext
|
67
|
+
g[:fix_id] = rule.fix.id
|
68
|
+
|
69
|
+
g[:check] = rule.check.content
|
70
|
+
g[:check_ref_name] = rule.check.content_ref.name
|
71
|
+
g[:check_ref] = rule.check.content_ref.href
|
72
|
+
[b.id, g]
|
73
|
+
end
|
74
|
+
mapped_benchmark_group.to_h
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: inspec_delta
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JP024221
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-09-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: facets
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.1.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.1.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: inspec-objects
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.1.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.1.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: inspec_tools
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.2.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.2.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: ruby2ruby
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.4.4
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.4.4
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: ruby_parser
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.14.2
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.14.2
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: thor
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.0.1
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.0.1
|
153
|
+
description: Quality of Life tools for managing inspec profiles.
|
154
|
+
email:
|
155
|
+
- jeremy.perron2@cerner.com
|
156
|
+
executables:
|
157
|
+
- inspec_delta
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- README.md
|
162
|
+
- bin/inspec_delta
|
163
|
+
- lib/inspec_delta.rb
|
164
|
+
- lib/inspec_delta/command.rb
|
165
|
+
- lib/inspec_delta/commands/profile.rb
|
166
|
+
- lib/inspec_delta/objects.rb
|
167
|
+
- lib/inspec_delta/objects/control.rb
|
168
|
+
- lib/inspec_delta/objects/profile.rb
|
169
|
+
- lib/inspec_delta/parsers.rb
|
170
|
+
- lib/inspec_delta/parsers/benchmark.rb
|
171
|
+
- lib/inspec_delta/version.rb
|
172
|
+
homepage: https://github.com/cerner/inspec_delta
|
173
|
+
licenses:
|
174
|
+
- APACHE
|
175
|
+
metadata: {}
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - "~>"
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '2.7'
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 2.7.1
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
requirements: []
|
194
|
+
rubygems_version: 3.1.2
|
195
|
+
signing_key:
|
196
|
+
specification_version: 4
|
197
|
+
summary: Quality of Life tools for managing inspec profiles.
|
198
|
+
test_files: []
|