inspec 2.2.78 → 2.2.101
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 +4 -4
- data/CHANGELOG.md +44 -14
- data/Rakefile +19 -0
- data/docs/profiles.md +106 -8
- data/examples/inheritance/inspec.yml +2 -1
- data/examples/profile/controls/gordon.rb +1 -1
- data/examples/profile/controls/meta.rb +2 -0
- data/examples/profile/inspec.yml +2 -1
- data/inspec.gemspec +3 -2
- data/lib/bundles/inspec-compliance/cli.rb +13 -1
- data/lib/bundles/inspec-compliance/http.rb +9 -18
- data/lib/bundles/inspec-compliance/target.rb +3 -3
- data/lib/fetchers/local.rb +60 -17
- data/lib/inspec.rb +4 -0
- data/lib/inspec/attribute_registry.rb +83 -0
- data/lib/inspec/base_cli.rb +10 -1
- data/lib/inspec/cli.rb +12 -1
- data/lib/inspec/control_eval_context.rb +13 -4
- data/lib/inspec/dependencies/cache.rb +1 -1
- data/lib/inspec/dependencies/dependency_set.rb +1 -1
- data/lib/inspec/dependencies/requirement.rb +2 -1
- data/lib/inspec/errors.rb +27 -0
- data/lib/inspec/file_provider.rb +38 -1
- data/lib/inspec/globals.rb +5 -0
- data/lib/inspec/impact.rb +34 -0
- data/lib/inspec/objects/attribute.rb +92 -7
- data/lib/inspec/profile.rb +33 -4
- data/lib/inspec/profile_context.rb +7 -7
- data/lib/inspec/profile_vendor.rb +21 -1
- data/lib/inspec/reporters/automate.rb +7 -2
- data/lib/inspec/reporters/cli.rb +12 -4
- data/lib/inspec/reporters/json.rb +3 -1
- data/lib/inspec/rspec_extensions.rb +12 -0
- data/lib/inspec/rule.rb +6 -1
- data/lib/inspec/runner.rb +2 -2
- data/lib/inspec/schema.rb +16 -2
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/mysql_session.rb +1 -0
- metadata +30 -6
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Impact scores based off CVSS 3.0
|
4
|
+
module Inspec::Impact
|
5
|
+
IMPACT_SCORES = {
|
6
|
+
'none' => 0.0,
|
7
|
+
'low' => 0.01,
|
8
|
+
'medium' => 0.4,
|
9
|
+
'high' => 0.7,
|
10
|
+
'critical' => 0.9,
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def self.impact_from_string(value)
|
14
|
+
# return if its a number
|
15
|
+
return value if is_number?(value)
|
16
|
+
raise Inspec::ImpactError, "'#{value}' is not a valid impact name. Valid impact names: none, low, medium, high, critical." unless IMPACT_SCORES.key?(value.downcase)
|
17
|
+
IMPACT_SCORES[value]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.is_number?(value)
|
21
|
+
Float(value)
|
22
|
+
true
|
23
|
+
rescue
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.string_from_impact(value)
|
28
|
+
value = value.to_f
|
29
|
+
raise Inspec::ImpactError, "'#{value}' is not a valid impact score. Valid impact scores: [0.0 - 1.0]." if value < 0 || value > 1
|
30
|
+
IMPACT_SCORES.reverse_each do |name, impact|
|
31
|
+
return name if value >= impact
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -3,7 +3,16 @@
|
|
3
3
|
module Inspec
|
4
4
|
class Attribute
|
5
5
|
attr_accessor :name
|
6
|
-
|
6
|
+
|
7
|
+
VALID_TYPES = %w{
|
8
|
+
String
|
9
|
+
Numeric
|
10
|
+
Regexp
|
11
|
+
Array
|
12
|
+
Hash
|
13
|
+
Boolean
|
14
|
+
Any
|
15
|
+
}.freeze
|
7
16
|
|
8
17
|
DEFAULT_ATTRIBUTE = Class.new do
|
9
18
|
def initialize(name)
|
@@ -16,7 +25,6 @@ module Inspec
|
|
16
25
|
"Use --attrs to provide a value for '#{@name}' or specify a default "\
|
17
26
|
"value with `attribute('#{@name}', default: 'somedefault', ...)`.",
|
18
27
|
)
|
19
|
-
|
20
28
|
self
|
21
29
|
end
|
22
30
|
|
@@ -28,16 +36,22 @@ module Inspec
|
|
28
36
|
def initialize(name, options = {})
|
29
37
|
@name = name
|
30
38
|
@opts = options
|
39
|
+
validate_value_type(default) if @opts.key?(:type) && @opts.key?(:default)
|
31
40
|
@value = nil
|
32
41
|
end
|
33
42
|
|
34
|
-
|
35
|
-
|
36
|
-
@value
|
43
|
+
def value=(new_value)
|
44
|
+
validate_value_type(new_value) if @opts.key?(:type)
|
45
|
+
@value = new_value
|
37
46
|
end
|
38
47
|
|
39
|
-
def
|
40
|
-
@
|
48
|
+
def value
|
49
|
+
if @value.nil?
|
50
|
+
validate_required(@value) if @opts[:required] == true
|
51
|
+
@value = default
|
52
|
+
else
|
53
|
+
@value
|
54
|
+
end
|
41
55
|
end
|
42
56
|
|
43
57
|
def title
|
@@ -71,5 +85,76 @@ module Inspec
|
|
71
85
|
def to_s
|
72
86
|
"Attribute #{@name} with #{@value}"
|
73
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def validate_required(value)
|
92
|
+
# value will be set already if a secrets file was passed in
|
93
|
+
if (!@opts.key?(:default) && value.nil?) || (@opts[:default].nil? && value.nil?)
|
94
|
+
error = Inspec::Attribute::RequiredError.new
|
95
|
+
error.attribute_name = @name
|
96
|
+
raise error, "Attribute '#{error.attribute_name}' is required and does not have a value."
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate_type(type)
|
101
|
+
type = type.capitalize
|
102
|
+
abbreviations = {
|
103
|
+
'Num' => 'Numeric',
|
104
|
+
'Regex' => 'Regexp',
|
105
|
+
}
|
106
|
+
type = abbreviations[type] if abbreviations.key?(type)
|
107
|
+
if !VALID_TYPES.include?(type)
|
108
|
+
error = Inspec::Attribute::TypeError.new
|
109
|
+
error.attribute_type = type
|
110
|
+
raise error, "Type '#{error.attribute_type}' is not a valid attribute type."
|
111
|
+
end
|
112
|
+
type
|
113
|
+
end
|
114
|
+
|
115
|
+
def valid_numeric?(value)
|
116
|
+
Float(value)
|
117
|
+
true
|
118
|
+
rescue
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
def valid_regexp?(value)
|
123
|
+
# check for invalid regex syntex
|
124
|
+
Regexp.new(value)
|
125
|
+
true
|
126
|
+
rescue
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
131
|
+
def validate_value_type(value)
|
132
|
+
type = validate_type(@opts[:type])
|
133
|
+
return if type == 'Any'
|
134
|
+
|
135
|
+
invalid_type = false
|
136
|
+
if type == 'Regexp'
|
137
|
+
invalid_type = true if !value.is_a?(String) || !valid_regexp?(value)
|
138
|
+
elsif type == 'Numeric'
|
139
|
+
invalid_type = true if !valid_numeric?(value)
|
140
|
+
elsif type == 'Boolean'
|
141
|
+
invalid_type = true if ![true, false].include?(value)
|
142
|
+
elsif value.is_a?(Module.const_get(type)) == false
|
143
|
+
invalid_type = true
|
144
|
+
end
|
145
|
+
|
146
|
+
if invalid_type == true
|
147
|
+
error = Inspec::Attribute::ValidationError.new
|
148
|
+
error.attribute_name = @name
|
149
|
+
error.attribute_value = value
|
150
|
+
error.attribute_type = type
|
151
|
+
raise error, "Attribute '#{error.attribute_name}' with value '#{error.attribute_value}' does not validate to type '#{error.attribute_type}'."
|
152
|
+
end
|
153
|
+
end
|
154
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
155
|
+
|
156
|
+
def default
|
157
|
+
@opts.key?(:default) ? @opts[:default] : DEFAULT_ATTRIBUTE.new(@name)
|
158
|
+
end
|
74
159
|
end
|
75
160
|
end
|
data/lib/inspec/profile.rb
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
|
6
6
|
require 'forwardable'
|
7
7
|
require 'openssl'
|
8
|
+
require 'inspec/attribute_registry'
|
8
9
|
require 'inspec/polyfill'
|
9
10
|
require 'inspec/cached_fetcher'
|
10
11
|
require 'inspec/file_provider'
|
@@ -55,7 +56,7 @@ module Inspec
|
|
55
56
|
file_provider = FileProvider.for_path(path)
|
56
57
|
rp = file_provider.relative_provider
|
57
58
|
|
58
|
-
# copy embedded
|
59
|
+
# copy embedded dependencies into global cache
|
59
60
|
copy_deps_into_cache(rp, opts) unless opts[:vendor_cache].nil?
|
60
61
|
|
61
62
|
reader = Inspec::SourceReader.resolve(rp)
|
@@ -79,7 +80,7 @@ module Inspec
|
|
79
80
|
end
|
80
81
|
|
81
82
|
attr_reader :source_reader, :backend, :runner_context, :check_mode
|
82
|
-
attr_accessor :parent_profile
|
83
|
+
attr_accessor :parent_profile, :profile_name
|
83
84
|
def_delegator :@source_reader, :tests
|
84
85
|
def_delegator :@source_reader, :libraries
|
85
86
|
def_delegator :@source_reader, :metadata
|
@@ -93,11 +94,13 @@ module Inspec
|
|
93
94
|
@controls = options[:controls] || []
|
94
95
|
@writable = options[:writable] || false
|
95
96
|
@profile_id = options[:id]
|
97
|
+
@profile_name = options[:profile_name]
|
96
98
|
@cache = options[:vendor_cache] || Cache.new
|
97
99
|
@attr_values = options[:attributes]
|
98
100
|
@tests_collected = false
|
99
101
|
@libraries_loaded = false
|
100
102
|
@check_mode = options[:check_mode] || false
|
103
|
+
@parent_profile = options[:parent_profile]
|
101
104
|
Metadata.finalize(@source_reader.metadata, @profile_id, options)
|
102
105
|
|
103
106
|
# if a backend has already been created, clone it so each profile has its own unique backend object
|
@@ -119,6 +122,17 @@ module Inspec
|
|
119
122
|
|
120
123
|
@supports_platform = metadata.supports_platform?(@backend)
|
121
124
|
@supports_runtime = metadata.supports_runtime?
|
125
|
+
register_metadata_attributes
|
126
|
+
end
|
127
|
+
|
128
|
+
def register_metadata_attributes
|
129
|
+
if metadata.params.key?(:attributes)
|
130
|
+
metadata.params[:attributes].each do |attribute|
|
131
|
+
attr_dup = attribute.dup
|
132
|
+
name = attr_dup.delete(:name)
|
133
|
+
@runner_context.register_attribute(name, attr_dup)
|
134
|
+
end
|
135
|
+
end
|
122
136
|
end
|
123
137
|
|
124
138
|
def name
|
@@ -229,7 +243,7 @@ module Inspec
|
|
229
243
|
info(load_params.dup)
|
230
244
|
end
|
231
245
|
|
232
|
-
def info(res = params.dup) # rubocop:disable Metrics/CyclomaticComplexity
|
246
|
+
def info(res = params.dup) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
233
247
|
# add information about the controls
|
234
248
|
res[:controls] = res[:controls].map do |id, rule|
|
235
249
|
next if id.to_s.empty?
|
@@ -239,6 +253,16 @@ module Inspec
|
|
239
253
|
data[:impact] = 1.0 if data[:impact] > 1.0
|
240
254
|
data[:impact] = 0.0 if data[:impact] < 0.0
|
241
255
|
data[:id] = id
|
256
|
+
|
257
|
+
# if the code field is empty try and pull info from dependencies
|
258
|
+
if data[:code].empty? && parent_profile.nil?
|
259
|
+
locked_dependencies.dep_list.each do |_name, dep|
|
260
|
+
profile = dep.profile
|
261
|
+
code = Inspec::MethodSource.code_at(data[:source_location], profile.source_reader)
|
262
|
+
data[:code] = code unless code.nil? || code.empty?
|
263
|
+
break if !data[:code].empty?
|
264
|
+
end
|
265
|
+
end
|
242
266
|
data
|
243
267
|
end.compact
|
244
268
|
|
@@ -249,7 +273,12 @@ module Inspec
|
|
249
273
|
end
|
250
274
|
|
251
275
|
# add information about the required attributes
|
252
|
-
|
276
|
+
if res[:attributes].nil? || res[:attributes].empty?
|
277
|
+
# convert to array for backwords compatability
|
278
|
+
res[:attributes] = []
|
279
|
+
else
|
280
|
+
res[:attributes] = res[:attributes].values.map(&:to_hash)
|
281
|
+
end
|
253
282
|
res[:sha256] = sha256
|
254
283
|
res[:parent_profile] = parent_profile unless parent_profile.nil?
|
255
284
|
|
@@ -18,7 +18,7 @@ module Inspec
|
|
18
18
|
'check_mode' => profile.check_mode })
|
19
19
|
end
|
20
20
|
|
21
|
-
attr_reader :attributes, :
|
21
|
+
attr_reader :attributes, :backend, :profile_name, :profile_id, :resource_registry
|
22
22
|
attr_accessor :rules
|
23
23
|
def initialize(profile_id, backend, conf)
|
24
24
|
if backend.nil?
|
@@ -28,12 +28,14 @@ module Inspec
|
|
28
28
|
@profile_id = profile_id
|
29
29
|
@backend = backend
|
30
30
|
@conf = conf.dup
|
31
|
+
@profile_name = @conf['profile'].profile_name || @profile_id if @conf['profile']
|
31
32
|
@skip_only_if_eval = @conf['check_mode']
|
32
33
|
@rules = {}
|
33
34
|
@control_subcontexts = []
|
34
35
|
@lib_subcontexts = []
|
35
36
|
@require_loader = ::Inspec::RequireLoader.new
|
36
|
-
@
|
37
|
+
Inspec::AttributeRegistry.register_profile_alias(@profile_id, @profile_name) if @profile_id != @profile_name
|
38
|
+
@attributes = Inspec::AttributeRegistry.list_attributes_for_profile(@profile_id)
|
37
39
|
# A local resource registry that only contains resources defined
|
38
40
|
# in the transitive dependency tree of the loaded profile.
|
39
41
|
@resource_registry = Inspec::Resource.new_registry
|
@@ -187,11 +189,9 @@ module Inspec
|
|
187
189
|
|
188
190
|
def register_attribute(name, options = {})
|
189
191
|
# we need to return an attribute object, to allow dermination of default values
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
@attributes.push(attr)
|
194
|
-
attr.value
|
192
|
+
attribute = Inspec::AttributeRegistry.register_attribute(name, @profile_id, options)
|
193
|
+
attribute.value = @conf['attributes'][name] unless @conf['attributes'].nil? || @conf['attributes'][name].nil?
|
194
|
+
attribute.value
|
195
195
|
end
|
196
196
|
|
197
197
|
def set_header(field, val)
|
@@ -8,7 +8,7 @@ module Inspec
|
|
8
8
|
attr_reader :profile_path
|
9
9
|
|
10
10
|
def initialize(path)
|
11
|
-
@profile_path = Pathname.new(path)
|
11
|
+
@profile_path = Pathname.new(File.expand_path(path))
|
12
12
|
end
|
13
13
|
|
14
14
|
def vendor!
|
@@ -56,11 +56,31 @@ module Inspec
|
|
56
56
|
def vendor_dependencies
|
57
57
|
delete_vendored_data
|
58
58
|
File.write(lockfile, profile.generate_lockfile.to_yaml)
|
59
|
+
extract_archives
|
59
60
|
end
|
60
61
|
|
61
62
|
def delete_vendored_data
|
62
63
|
FileUtils.rm_rf(cache_path) if cache_path.exist?
|
63
64
|
File.delete(lockfile) if lockfile.exist?
|
64
65
|
end
|
66
|
+
|
67
|
+
def extract_archives
|
68
|
+
Dir.glob(File.join(cache_path, '*')).each do |filepath|
|
69
|
+
# Get SHA without extension
|
70
|
+
# We use split since '.' is not valid in a SHA checksum
|
71
|
+
destination_dir_name = File.basename(filepath).split('.')[0]
|
72
|
+
destination_path = File.join(cache_path, destination_dir_name)
|
73
|
+
|
74
|
+
provider = FileProvider.for_path(filepath)
|
75
|
+
|
76
|
+
next unless provider.is_a?(ZipProvider) || provider.is_a?(TarProvider)
|
77
|
+
|
78
|
+
Inspec::Log.debug("Extracting '#{filepath}' to '#{destination_path}'")
|
79
|
+
provider.extract(destination_path)
|
80
|
+
|
81
|
+
Inspec::Log.debug("Deleting archive '#{filepath}'")
|
82
|
+
File.delete(filepath)
|
83
|
+
end
|
84
|
+
end
|
65
85
|
end
|
66
86
|
end
|
@@ -53,8 +53,13 @@ module Inspec::Reporters
|
|
53
53
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
54
54
|
end
|
55
55
|
|
56
|
-
http.request(req)
|
57
|
-
|
56
|
+
res = http.request(req)
|
57
|
+
if res.is_a?(Net::HTTPSuccess)
|
58
|
+
return true
|
59
|
+
else
|
60
|
+
Inspec::Log.error "send_report: POST to #{uri.path} returned: #{res.body}"
|
61
|
+
return false
|
62
|
+
end
|
58
63
|
rescue => e
|
59
64
|
Inspec::Log.error "send_report: POST to #{uri.path} returned: #{e.message}"
|
60
65
|
return false
|
data/lib/inspec/reporters/cli.rb
CHANGED
@@ -63,9 +63,17 @@ module Inspec::Reporters
|
|
63
63
|
private
|
64
64
|
|
65
65
|
def print_profile_header(profile)
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
header = {
|
67
|
+
'Profile' => format_profile_name(profile),
|
68
|
+
'Version' => profile[:version] || '(not specified)',
|
69
|
+
}
|
70
|
+
header['Target'] = run_data[:platform][:target] unless run_data[:platform][:target].nil?
|
71
|
+
header['Target ID'] = @config['target_id'] unless @config['target_id'].nil?
|
72
|
+
|
73
|
+
pad = header.keys.max_by(&:length).length + 1
|
74
|
+
header.each do |title, value|
|
75
|
+
output(format("%-#{pad}s %s", title + ':', value))
|
76
|
+
end
|
69
77
|
output('')
|
70
78
|
end
|
71
79
|
|
@@ -141,7 +149,7 @@ module Inspec::Reporters
|
|
141
149
|
|
142
150
|
message_to_format = ''
|
143
151
|
message_to_format += "#{INDICATORS[indicator]} " unless indicator.nil?
|
144
|
-
message_to_format += message.to_s.lstrip
|
152
|
+
message_to_format += message.to_s.lstrip.force_encoding(Encoding::UTF_8)
|
145
153
|
|
146
154
|
format_with_color(color, indent_lines(message_to_format, indentation))
|
147
155
|
end
|
@@ -22,10 +22,12 @@ module Inspec::Reporters
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def platform
|
25
|
-
{
|
25
|
+
platform = {
|
26
26
|
name: run_data[:platform][:name],
|
27
27
|
release: run_data[:platform][:release],
|
28
28
|
}
|
29
|
+
platform[:target_id] = @config['target_id'] if @config['target_id']
|
30
|
+
platform
|
29
31
|
end
|
30
32
|
|
31
33
|
def profile_results(control)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'inspec/attribute_registry'
|
2
|
+
require 'rspec/core/example_group'
|
3
|
+
|
4
|
+
# This file allows you to add ExampleGroups to be used in rspec tests
|
5
|
+
#
|
6
|
+
class RSpec::Core::ExampleGroup
|
7
|
+
# This DSL method allows us to access the values of attributes within InSpec tests
|
8
|
+
def attribute(name)
|
9
|
+
Inspec::AttributeRegistry.find_attribute(name, self.class.metadata[:profile_id]).value
|
10
|
+
end
|
11
|
+
define_example_method :attribute
|
12
|
+
end
|
data/lib/inspec/rule.rb
CHANGED
data/lib/inspec/runner.rb
CHANGED
@@ -52,7 +52,7 @@ module Inspec
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# list of profile attributes
|
55
|
-
@attributes =
|
55
|
+
@attributes = {}
|
56
56
|
|
57
57
|
load_attributes(@conf)
|
58
58
|
configure_transport
|
@@ -88,7 +88,7 @@ module Inspec
|
|
88
88
|
@test_collector.add_profile(requirement.profile)
|
89
89
|
end
|
90
90
|
|
91
|
-
@attributes
|
91
|
+
@attributes = profile.runner_context.attributes if @attributes.empty?
|
92
92
|
all_controls += profile.collect_tests
|
93
93
|
end
|
94
94
|
|