inspec 1.48.0 → 1.49.2

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