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.
@@ -0,0 +1,5 @@
1
+ module Inspec
2
+ def self.config_dir
3
+ ENV['INSPEC_CONFIG_DIR'] ? ENV['INSPEC_CONFIG_DIR'] : File.join(Dir.home, '.inspec')
4
+ end
5
+ end
@@ -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
- attr_writer :value
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
- # implicit call is done by inspec to determine the value of an attribute
35
- def value
36
- @value.nil? ? default : @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 default
40
- @opts.key?(:default) ? @opts[:default] : DEFAULT_ATTRIBUTE.new(@name)
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
@@ -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 dependecies into global cache
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
- res[:attributes] = res[:attributes].map(&:to_hash) unless res[:attributes].nil? || res[:attributes].empty?
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, :profile_id, :resource_registry, :backend
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
- @attributes = []
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
- attr = Attribute.new(name, options)
191
- # read value from given gived values
192
- attr.value = @conf['attributes'][attr.name] unless @conf['attributes'].nil?
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
- return true
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
@@ -63,9 +63,17 @@ module Inspec::Reporters
63
63
  private
64
64
 
65
65
  def print_profile_header(profile)
66
- output("Profile: #{format_profile_name(profile)}")
67
- output("Version: #{profile[:version] || '(not specified)'}")
68
- output("Target: #{run_data[:platform][:target]}") unless run_data[:platform][:target].nil?
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
@@ -75,7 +75,12 @@ module Inspec
75
75
  end
76
76
 
77
77
  def impact(v = nil)
78
- @impact = v unless v.nil?
78
+ if v.is_a?(String)
79
+ @impact = Inspec::Impact.impact_from_string(v)
80
+ elsif !v.nil?
81
+ @impact = v
82
+ end
83
+
79
84
  @impact
80
85
  end
81
86
 
@@ -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 |= profile.runner_context.attributes
91
+ @attributes = profile.runner_context.attributes if @attributes.empty?
92
92
  all_controls += profile.collect_tests
93
93
  end
94
94