inspec 1.48.0 → 1.49.2

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/CHANGELOG.md +40 -16
  4. data/Rakefile +1 -1
  5. data/docs/resources/bond.md.erb +6 -1
  6. data/docs/resources/mysql_session.md.erb +24 -12
  7. data/docs/resources/passwd.md.erb +1 -1
  8. data/docs/resources/xml.md.erb +7 -2
  9. data/docs/shell.md +22 -0
  10. data/inspec.gemspec +1 -1
  11. data/lib/bundles/inspec-artifact/cli.rb +0 -2
  12. data/lib/bundles/inspec-compliance/api.rb +58 -3
  13. data/lib/bundles/inspec-compliance/cli.rb +1 -1
  14. data/lib/bundles/inspec-habitat/profile.rb +1 -1
  15. data/lib/fetchers/url.rb +1 -1
  16. data/lib/inspec/base_cli.rb +3 -1
  17. data/lib/inspec/cli.rb +11 -1
  18. data/lib/inspec/control_eval_context.rb +13 -2
  19. data/lib/inspec/dependencies/lockfile.rb +0 -2
  20. data/lib/inspec/dsl_shared.rb +8 -0
  21. data/lib/inspec/library_eval_context.rb +12 -1
  22. data/lib/inspec/metadata.rb +13 -44
  23. data/lib/inspec/objects/attribute.rb +1 -1
  24. data/lib/inspec/plugins/resource.rb +18 -2
  25. data/lib/inspec/profile.rb +17 -11
  26. data/lib/inspec/profile_context.rb +9 -3
  27. data/lib/inspec/profile_vendor.rb +1 -1
  28. data/lib/inspec/resource.rb +5 -0
  29. data/lib/inspec/rspec_json_formatter.rb +3 -3
  30. data/lib/inspec/rule.rb +1 -1
  31. data/lib/inspec/runner.rb +13 -5
  32. data/lib/inspec/schema.rb +1 -1
  33. data/lib/inspec/shell.rb +1 -1
  34. data/lib/inspec/version.rb +1 -1
  35. data/lib/resources/aide_conf.rb +0 -2
  36. data/lib/resources/apache_conf.rb +9 -2
  37. data/lib/resources/auditd.rb +0 -1
  38. data/lib/resources/auditd_rules.rb +0 -2
  39. data/lib/resources/bond.rb +4 -0
  40. data/lib/resources/crontab.rb +1 -1
  41. data/lib/resources/docker.rb +1 -1
  42. data/lib/resources/elasticsearch.rb +1 -1
  43. data/lib/resources/file.rb +2 -0
  44. data/lib/resources/groups.rb +29 -5
  45. data/lib/resources/grub_conf.rb +1 -1
  46. data/lib/resources/os.rb +8 -20
  47. data/lib/resources/package.rb +20 -21
  48. data/lib/resources/platform.rb +112 -0
  49. data/lib/resources/port.rb +1 -1
  50. data/lib/resources/processes.rb +1 -1
  51. data/lib/resources/registry_key.rb +1 -1
  52. data/lib/resources/service.rb +1 -1
  53. data/lib/resources/virtualization.rb +1 -1
  54. data/lib/resources/x509_certificate.rb +1 -1
  55. data/lib/resources/xml.rb +1 -0
  56. metadata +5 -10
@@ -2,7 +2,7 @@
2
2
  require 'json'
3
3
 
4
4
  module Inspec
5
- class Schema # rubocop:disable Metrics/ClassLength
5
+ class Schema
6
6
  STATISTICS = {
7
7
  'type' => 'object',
8
8
  'additionalProperties' => false,
@@ -9,7 +9,7 @@ module Inspec
9
9
  # A pry based shell for inspec. Given a runner (with a configured backend and
10
10
  # all that jazz), this shell will produce a pry shell from which you can run
11
11
  # inspec/ruby commands that will be run within the context of the runner.
12
- class Shell # rubocop:disable Metrics/ClassLength
12
+ class Shell
13
13
  def initialize(runner)
14
14
  @runner = runner
15
15
  end
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.48.0'
7
+ VERSION = '1.49.2'
8
8
  end
@@ -3,8 +3,6 @@
3
3
 
4
4
  require 'utils/filter'
5
5
  require 'utils/parser'
6
-
7
- # rubocop:disable Metrics/ClassLength
8
6
  module Inspec::Resources
9
7
  class AideConf < Inspec.resource(1)
10
8
  name 'aide_conf'
@@ -78,10 +78,17 @@ module Inspec::Resources
78
78
  raw_conf = read_file(to_read[0])
79
79
  @content += raw_conf
80
80
 
81
- # parse include file parameters
81
+ # An explaination of the below regular expression.
82
+ # Creates two capture groups.
83
+ # The first group captures the first group of non-whitespace character
84
+ # surrounded whitespace characters.
85
+ # The second group contains a conditional with a positive lookahead
86
+ # (does the line end with one or more spaces?). If the lookahead succeeds
87
+ # a non-greedy capture takes place, if it fails then a greedy capture takes place.
88
+ # The regex is terminated by an expression that matches zero or more spaces.
82
89
  params = SimpleConfig.new(
83
90
  raw_conf,
84
- assignment_regex: /^\s*(\S+)\s+(.*)\s*$/,
91
+ assignment_regex: /^\s*(\S+)\s+((?=.*\s+$).*?|.*)\s*$/,
85
92
  multiple_values: true,
86
93
  ).params
87
94
  @params.merge!(params)
@@ -9,7 +9,6 @@ require 'utils/filter'
9
9
  require 'utils/parser'
10
10
 
11
11
  module Inspec::Resources
12
- # rubocop:disable Metrics/ClassLength
13
12
  class AuditDaemon < Inspec.resource(1)
14
13
  extend Forwardable
15
14
  attr_accessor :lines
@@ -43,8 +43,6 @@ module Inspec::Resources
43
43
  'Audit Daemon Rules (for auditd version < 2.3)'
44
44
  end
45
45
  end
46
-
47
- # rubocop:disable Metrics/ClassLength
48
46
  class AuditDaemonRules < Inspec.resource(1)
49
47
  extend Forwardable
50
48
  attr_accessor :rules, :lines
@@ -58,6 +58,10 @@ module Inspec::Resources
58
58
  params['Slave Interface']
59
59
  end
60
60
 
61
+ def mode
62
+ params['Bonding Mode'].first
63
+ end
64
+
61
65
  def to_s
62
66
  "Bond #{@bond}"
63
67
  end
@@ -4,7 +4,7 @@ require 'utils/parser'
4
4
  require 'utils/filter'
5
5
 
6
6
  module Inspec::Resources
7
- class Crontab < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
7
+ class Crontab < Inspec.resource(1)
8
8
  name 'crontab'
9
9
  desc 'Use the crontab InSpec audit resource to test the contents of the crontab for a given user which contains information about scheduled tasks owned by that user.'
10
10
  example "
@@ -63,7 +63,7 @@ module Inspec::Resources
63
63
  # For compatability with Serverspec we also offer the following resouses:
64
64
  # - docker_container
65
65
  # - docker_image
66
- class Docker < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
66
+ class Docker < Inspec.resource(1)
67
67
  name 'docker'
68
68
 
69
69
  desc "
@@ -5,7 +5,7 @@ require 'hashie/mash'
5
5
  require 'resources/package'
6
6
 
7
7
  module Inspec::Resources
8
- class Elasticsearch < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
8
+ class Elasticsearch < Inspec.resource(1)
9
9
  name 'elasticsearch'
10
10
  desc "Use the Elasticsearch InSpec audit resource to test the status of nodes in
11
11
  an Elasticsearch cluster."
@@ -269,6 +269,8 @@ module Inspec::Resources
269
269
  translate_perm_names('full-control') + %w{ChangePermissions}
270
270
  when 'take-ownership'
271
271
  translate_perm_names('full-control') + %w{TakeOwnership}
272
+ when 'synchronize'
273
+ translate_perm_names('full-control') + %w{Synchronize}
272
274
  end
273
275
  end
274
276
 
@@ -13,11 +13,13 @@ module Inspec::Resources
13
13
  # select group provider based on the operating system
14
14
  # returns nil, if no group manager was found for the operating system
15
15
  def select_group_manager(os)
16
- if os.unix?
17
- @group_provider = UnixGroup.new(inspec)
18
- elsif os.windows?
19
- @group_provider = WindowsGroup.new(inspec)
20
- end
16
+ @group_provider = if os.darwin?
17
+ DarwinGroup.new(inspec)
18
+ elsif os.unix?
19
+ UnixGroup.new(inspec)
20
+ elsif os.windows?
21
+ WindowsGroup.new(inspec)
22
+ end
21
23
  end
22
24
  end
23
25
 
@@ -154,6 +156,28 @@ module Inspec::Resources
154
156
  end
155
157
  end
156
158
 
159
+ # OSX uses opendirectory for groups, so `/etc/group` may not be fully accurate
160
+ # This uses `dscacheutil` to get the group info instead of `etc_group`
161
+ class DarwinGroup < GroupInfo
162
+ def groups
163
+ group_info = inspec.command('dscacheutil -q group').stdout.split("\n\n")
164
+
165
+ groups = []
166
+ regex = /^([^:]*?)\s*:\s(.*?)\s*$/
167
+ group_info.each do |data|
168
+ groups << inspec.parse_config(data, assignment_regex: regex).params
169
+ end
170
+
171
+ # Convert the `dscacheutil` groups to match `inspec.etc_group.entries`
172
+ groups.each { |g| g['gid'] = g['gid'].to_i }
173
+ groups.each do |g|
174
+ next if g['users'].nil?
175
+ g['members'] = g.delete('users')
176
+ g['members'].tr!(' ', ',')
177
+ end
178
+ end
179
+ end
180
+
157
181
  class WindowsGroup < GroupInfo
158
182
  # returns all local groups
159
183
  def groups
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'utils/simpleconfig'
5
5
 
6
- class GrubConfig < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
6
+ class GrubConfig < Inspec.resource(1)
7
7
  name 'grub_conf'
8
8
  desc 'Use the grub_conf InSpec audit resource to test the boot config of Linux systems that use Grub.'
9
9
  example "
@@ -2,8 +2,10 @@
2
2
  # author: Dominik Richter
3
3
  # author: Christoph Hartmann
4
4
 
5
+ require 'resources/platform'
6
+
5
7
  module Inspec::Resources
6
- class OSResource < Inspec.resource(1)
8
+ class OSResource < PlatformResource
7
9
  name 'os'
8
10
  desc 'Use the os InSpec audit resource to test the platform on which the system is running.'
9
11
  example "
@@ -23,31 +25,17 @@ module Inspec::Resources
23
25
  # reuse helper methods from backend
24
26
  %w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux? darwin?}.each do |os_family|
25
27
  define_method(os_family.to_sym) do
26
- inspec.backend.os.send(os_family)
27
- end
28
- end
29
-
30
- def [](name)
31
- # convert string to symbol
32
- name = name.to_sym if name.is_a? String
33
- inspec.backend.os[name]
34
- end
35
-
36
- # add helper methods for easy access of properties
37
- # allows users to use os.name, os.family, os.release, os.arch
38
- %w{name family release arch}.each do |property|
39
- define_method(property.to_sym) do
40
- inspec.backend.os[property.to_sym]
28
+ @platform.send(os_family)
41
29
  end
42
30
  end
43
31
 
44
32
  # helper to collect a hash object easily
45
33
  def params
46
34
  {
47
- name: inspec.backend.os[:name],
48
- family: inspec.backend.os[:family],
49
- release: inspec.backend.os[:release],
50
- arch: inspec.backend.os[:arch],
35
+ name: name,
36
+ family: @platform[:family],
37
+ release: @platform[:release],
38
+ arch: @platform[:arch],
51
39
  }
52
40
  end
53
41
 
@@ -20,7 +20,7 @@ module Inspec::Resources
20
20
  end
21
21
  "
22
22
 
23
- def initialize(package_name = nil, opts = {}) # rubocop:disable Metrics/AbcSize
23
+ def initialize(package_name, opts = {}) # rubocop:disable Metrics/AbcSize
24
24
  @package_name = package_name
25
25
  @name = @package_name
26
26
  @cache = nil
@@ -45,7 +45,7 @@ module Inspec::Resources
45
45
  elsif ['hpux'].include?(os[:family])
46
46
  @pkgman = HpuxPkg.new(inspec)
47
47
  else
48
- return skip_resource 'The `package` resource is not supported on your OS yet.'
48
+ raise Inspec::Exceptions::ResourceSkipped, 'The `package` resource is not supported on your OS yet.'
49
49
  end
50
50
 
51
51
  evaluate_missing_requirements
@@ -53,28 +53,23 @@ module Inspec::Resources
53
53
 
54
54
  # returns true if the package is installed
55
55
  def installed?(_provider = nil, _version = nil)
56
- return false if info.nil?
57
56
  info[:installed] == true
58
57
  end
59
58
 
60
59
  # returns true it the package is held (if the OS supports it)
61
60
  def held?(_provider = nil, _version = nil)
62
- return false if info.nil?
63
- return false unless info.key?(:held)
64
61
  info[:held] == true
65
62
  end
66
63
 
67
64
  # returns the package description
68
65
  def info
69
66
  return @cache if !@cache.nil?
70
- return nil if @pkgman.nil?
71
67
  @pkgman.info(@package_name)
72
68
  end
73
69
 
74
70
  # return the package version
75
71
  def version
76
72
  info = @pkgman.info(@package_name)
77
- return nil if info.nil?
78
73
  info[:version]
79
74
  end
80
75
 
@@ -87,7 +82,7 @@ module Inspec::Resources
87
82
  def evaluate_missing_requirements
88
83
  missing_requirements_string = @pkgman.missing_requirements.uniq.join(', ')
89
84
  return if missing_requirements_string.empty?
90
- skip_resource "The following requirements are not met for this resource: #{missing_requirements_string}"
85
+ raise Inspec::Exceptions::ResourceSkipped, "The following requirements are not met for this resource: #{missing_requirements_string}"
91
86
  end
92
87
  end
93
88
 
@@ -99,7 +94,7 @@ module Inspec::Resources
99
94
 
100
95
  def missing_requirements
101
96
  # Each provider can provide an Array of missing requirements that will be
102
- # combined into a `skip_resource` message
97
+ # combined into a `ResourceSkipped` exception message.
103
98
  []
104
99
  end
105
100
  end
@@ -108,7 +103,7 @@ module Inspec::Resources
108
103
  class Deb < PkgManagement
109
104
  def info(package_name)
110
105
  cmd = inspec.command("dpkg -s #{package_name}")
111
- return nil if cmd.exit_status.to_i != 0
106
+ return {} if cmd.exit_status.to_i != 0
112
107
 
113
108
  params = SimpleConfig.new(
114
109
  cmd.stdout.chomp,
@@ -152,7 +147,7 @@ module Inspec::Resources
152
147
  cmd = inspec.command(rpm_cmd)
153
148
  # CentOS does not return an error code if the package is not installed,
154
149
  # therefore we need to check for emptyness
155
- return nil if cmd.exit_status.to_i != 0 || cmd.stdout.chomp.empty?
150
+ return {} if cmd.exit_status.to_i != 0 || cmd.stdout.chomp.empty?
156
151
  params = SimpleConfig.new(
157
152
  cmd.stdout.chomp,
158
153
  assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
@@ -195,7 +190,7 @@ module Inspec::Resources
195
190
  def info(package_name)
196
191
  brew_path = inspec.command('brew').exist? ? 'brew' : '/usr/local/bin/brew'
197
192
  cmd = inspec.command("#{brew_path} info --json=v1 #{package_name}")
198
- return nil if cmd.exit_status.to_i != 0
193
+ return {} if cmd.exit_status.to_i != 0
199
194
  # parse data
200
195
  pkg = JSON.parse(cmd.stdout)[0]
201
196
  {
@@ -204,8 +199,10 @@ module Inspec::Resources
204
199
  version: pkg['installed'][0]['version'],
205
200
  type: 'brew',
206
201
  }
207
- rescue JSON::ParserError => _e
208
- return nil
202
+ rescue JSON::ParserError => e
203
+ raise Inspec::Exceptions::ResourceFailed,
204
+ 'Failed to parse JSON from `brew` command. ' \
205
+ "Error: #{e}"
209
206
  end
210
207
  end
211
208
 
@@ -213,7 +210,7 @@ module Inspec::Resources
213
210
  class Pacman < PkgManagement
214
211
  def info(package_name)
215
212
  cmd = inspec.command("pacman -Qi #{package_name}")
216
- return nil if cmd.exit_status.to_i != 0
213
+ return {} if cmd.exit_status.to_i != 0
217
214
 
218
215
  params = SimpleConfig.new(
219
216
  cmd.stdout.chomp,
@@ -233,7 +230,7 @@ module Inspec::Resources
233
230
  class HpuxPkg < PkgManagement
234
231
  def info(package_name)
235
232
  cmd = inspec.command("swlist -l product | grep #{package_name}")
236
- return nil if cmd.exit_status.to_i != 0
233
+ return {} if cmd.exit_status.to_i != 0
237
234
  pkg = cmd.stdout.strip.split(' ')
238
235
  {
239
236
  name: pkg[0],
@@ -268,8 +265,10 @@ module Inspec::Resources
268
265
 
269
266
  begin
270
267
  package = JSON.parse(cmd.stdout)
271
- rescue JSON::ParserError => _e
272
- return nil
268
+ rescue JSON::ParserError => e
269
+ raise Inspec::Exceptions::ResourceFailed,
270
+ 'Failed to parse JSON from PowerShell. ' \
271
+ "Error: #{e}"
273
272
  end
274
273
 
275
274
  # What if we match multiple packages? just pick the first one for now.
@@ -288,7 +287,7 @@ module Inspec::Resources
288
287
  class BffPkg < PkgManagement
289
288
  def info(package_name)
290
289
  cmd = inspec.command("lslpp -cL #{package_name}")
291
- return nil if cmd.exit_status.to_i != 0
290
+ return {} if cmd.exit_status.to_i != 0
292
291
 
293
292
  bff_pkg = cmd.stdout.split("\n").last.split(':')
294
293
  {
@@ -313,7 +312,7 @@ module Inspec::Resources
313
312
  # solaris 10
314
313
  def solaris10_info(package_name)
315
314
  cmd = inspec.command("pkginfo -l #{package_name}")
316
- return nil if cmd.exit_status.to_i != 0
315
+ return {} if cmd.exit_status.to_i != 0
317
316
 
318
317
  params = SimpleConfig.new(
319
318
  cmd.stdout.chomp,
@@ -334,7 +333,7 @@ module Inspec::Resources
334
333
  # solaris 11
335
334
  def solaris11_info(package_name)
336
335
  cmd = inspec.command("pkg info #{package_name}")
337
- return nil if cmd.exit_status.to_i != 0
336
+ return {} if cmd.exit_status.to_i != 0
338
337
 
339
338
  params = SimpleConfig.new(
340
339
  cmd.stdout.chomp,
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+
3
+ module Inspec::Resources
4
+ class PlatformResource < Inspec.resource(1)
5
+ name 'platform'
6
+ desc 'Use the platform InSpec resource to test the platform on which the system is running.'
7
+ example "
8
+ describe platform do
9
+ its('name') { should eq 'redhat' }
10
+ end
11
+
12
+ describe platform do
13
+ it { should be_in_family('unix') }
14
+ end
15
+ "
16
+
17
+ def initialize
18
+ @platform = inspec.backend.os
19
+ end
20
+
21
+ # add helper methods for easy access of properties
22
+ %w{family release arch}.each do |property|
23
+ define_method(property.to_sym) do
24
+ @platform.send(property)
25
+ end
26
+ end
27
+
28
+ # This is a string override for platform.name.
29
+ # TODO: removed in inspec 2.0
30
+ class NameCleaned < String
31
+ def ==(other)
32
+ if other =~ /[A-Z ]/
33
+ cleaned = other.downcase.tr(' ', '_')
34
+ Inspec::Log.warn "[DEPRECATED] Platform names will become lowercase in InSpec 2.0. Please match on '#{cleaned}' instead of '#{other}'"
35
+ super(cleaned)
36
+ else
37
+ super(other)
38
+ end
39
+ end
40
+ end
41
+
42
+ def name
43
+ NameCleaned.new(@platform.name)
44
+ end
45
+
46
+ def [](key)
47
+ # convert string to symbol
48
+ key = key.to_sym if key.is_a? String
49
+ return name if key == :name
50
+
51
+ @platform[key]
52
+ end
53
+
54
+ def platform?(name)
55
+ @platform.name == name ||
56
+ @platform.family_hierarchy.include?(name)
57
+ end
58
+
59
+ def in_family?(family)
60
+ @platform.family_hierarchy.include?(family)
61
+ end
62
+
63
+ def families
64
+ @platform.family_hierarchy
65
+ end
66
+
67
+ def supported?(supports)
68
+ return true if supports.nil? || supports.empty?
69
+
70
+ status = true
71
+ supports.each do |s|
72
+ s.each do |k, v|
73
+ # ignore the inspec check for supports
74
+ # TODO: remove in inspec 2.0
75
+ if k == :inspec
76
+ next
77
+ elsif %i(os_family os-family platform_family platform-family).include?(k)
78
+ status = in_family?(v)
79
+ elsif %i(os platform).include?(k)
80
+ status = platform?(v)
81
+ elsif %i(os_name os-name platform_name platform-name).include?(k)
82
+ status = name == v
83
+ elsif k == :release
84
+ status = check_release(v)
85
+ else
86
+ status = false
87
+ end
88
+ break if status == false
89
+ end
90
+ return true if status == true
91
+ end
92
+
93
+ status
94
+ end
95
+
96
+ def to_s
97
+ 'Platform Detection'
98
+ end
99
+
100
+ private
101
+
102
+ def check_release(value)
103
+ # allow wild card matching
104
+ if value.include?('*')
105
+ cleaned = Regexp.escape(value).gsub('\*', '.*?')
106
+ !(release =~ /#{cleaned}/).nil?
107
+ else
108
+ release == value
109
+ end
110
+ end
111
+ end
112
+ end