inspec 2.2.78 → 2.2.101

Sign up to get free protection for your applications and to get access to all the features.
@@ -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