inspec 0.33.2 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,44 +2,142 @@
2
2
  # author: Christoph Hartmann
3
3
  # author: Dominik Richter
4
4
 
5
- # Usage:
6
- #
7
- # describe user('root') do
8
- # it { should exist }
9
- # its('uid') { should eq 0 }
10
- # its('gid') { should eq 0 }
11
- # its('group') { should eq 'root' }
12
- # its('groups') { should eq ['root', 'wheel']}
13
- # its('home') { should eq '/root' }
14
- # its('shell') { should eq '/bin/bash' }
15
- # its('mindays') { should eq 0 }
16
- # its('maxdays') { should eq 99 }
17
- # its('warndays') { should eq 5 }
18
- # end
19
- #
20
- # The following Serverspec matchers are deprecated in favor for direct value access
21
- #
22
- # describe user('root') do
23
- # it { should belong_to_group 'root' }
24
- # it { should have_uid 0 }
25
- # it { should have_home_directory '/root' }
26
- # it { should have_login_shell '/bin/bash' }
27
- # its('minimum_days_between_password_change') { should eq 0 }
28
- # its('maximum_days_between_password_change') { should eq 99 }
29
- # end
30
-
31
- # ServerSpec tests that are not supported:
32
- #
33
- # describe user('root') do
34
- # it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
35
- # its(:encrypted_password) { should eq 1234 }
36
- # end
37
-
38
5
  require 'utils/parser'
39
6
  require 'utils/convert'
40
7
 
41
8
  module Inspec::Resources
42
- class User < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
9
+ # This file contains two resources, the `user` and `users` resource.
10
+ # The `user` resource is optimized for requests that verify specific users
11
+ # that you know upfront for testing. If you need to query all users or find
12
+ # specific users with certain properties, use the `users` resource.
13
+ module UserManagementSelector
14
+ # select user provider based on the operating system
15
+ # returns nil, if no user manager was found for the operating system
16
+ def select_user_manager(os)
17
+ if os.linux?
18
+ LinuxUser.new(inspec)
19
+ elsif os.windows?
20
+ WindowsUser.new(inspec)
21
+ elsif ['darwin'].include?(os[:family])
22
+ DarwinUser.new(inspec)
23
+ elsif ['freebsd'].include?(os[:family])
24
+ FreeBSDUser.new(inspec)
25
+ elsif ['aix'].include?(os[:family])
26
+ AixUser.new(inspec)
27
+ elsif os.solaris?
28
+ SolarisUser.new(inspec)
29
+ elsif ['hpux'].include?(os[:family])
30
+ HpuxUser.new(inspec)
31
+ end
32
+ end
33
+ end
34
+
35
+ # The InSpec users resources looksup all local users available on a system.
36
+ # TODO: the current version of the users resource will use eg. /etc/passwd
37
+ # on Linux to parse all usernames. Therefore the resource may not return
38
+ # users managed on other systems like LDAP/ActiveDirectory. Please open
39
+ # a feature request at https://github.com/chef/inspec if you need that
40
+ # functionality
41
+ #
42
+ # This resource allows complex filter mechanisms
43
+ #
44
+ # describe users.where(uid: 0).entries do
45
+ # it { should eq ['root'] }
46
+ # its('uids') { should eq [1234] }
47
+ # its('gids') { should eq [1234] }
48
+ # end
49
+ #
50
+ # describe users.where { uid =~ /S\-1\-5\-21\-\d+\-\d+\-\d+\-500/ } do
51
+ # it { should exist }
52
+ # end
53
+ class Users < Inspec.resource(1)
54
+ include UserManagementSelector
55
+
56
+ name 'users'
57
+ desc 'Use the users InSpec audit resource to test local user profiles. Users can be filtered by groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
58
+ example "
59
+ describe users.where(uid: 0).entries do
60
+ it { should eq ['root'] }
61
+ its('uids') { should eq [1234] }
62
+ its('gids') { should eq [1234] }
63
+ end
64
+ "
65
+ def initialize
66
+ # select user provider
67
+ @user_provider = select_user_manager(inspec.os)
68
+ return skip_resource 'The `user` resource is not supported on your OS yet.' if @user_provider.nil?
69
+ end
70
+
71
+ filter = FilterTable.create
72
+ filter.add_accessor(:where)
73
+ .add_accessor(:entries)
74
+ .add(:usernames, field: :username)
75
+ .add(:uids, field: :uid)
76
+ .add(:gids, field: :gid)
77
+ .add(:groupnames, field: :groupname)
78
+ .add(:groups, field: :groups)
79
+ .add(:homes, field: :home)
80
+ .add(:shells, field: :shell)
81
+ .add(:mindays, field: :mindays)
82
+ .add(:maxdays, field: :maxdays)
83
+ .add(:warndays, field: :warndays)
84
+ .add(:disabled, field: :disabled)
85
+ .add(:exists?) { |x| !x.entries.empty? }
86
+ .add(:disabled?) { |x| x.where { disabled == false }.entries.empty? }
87
+ .add(:enabled?) { |x| x.where { disabled == true }.entries.empty? }
88
+ filter.connect(self, :collect_user_details)
89
+
90
+ def to_s
91
+ 'Users'
92
+ end
93
+
94
+ private
95
+
96
+ # method to get all available users
97
+ def list_users
98
+ @username_cache ||= @user_provider.list_users unless @user_provider.nil?
99
+ end
100
+
101
+ # collects information about every user
102
+ def collect_user_details
103
+ @users_cache ||= @user_provider.collect_user_details unless @user_provider.nil?
104
+ end
105
+ end
106
+
107
+ # The `user` resource handles the special case where only one resource is required
108
+ #
109
+ # describe user('root') do
110
+ # it { should exist }
111
+ # its('uid') { should eq 0 }
112
+ # its('gid') { should eq 0 }
113
+ # its('group') { should eq 'root' }
114
+ # its('groups') { should eq ['root', 'wheel']}
115
+ # its('home') { should eq '/root' }
116
+ # its('shell') { should eq '/bin/bash' }
117
+ # its('mindays') { should eq 0 }
118
+ # its('maxdays') { should eq 99 }
119
+ # its('warndays') { should eq 5 }
120
+ # end
121
+ #
122
+ # The following Serverspec matchers are deprecated in favor for direct value access
123
+ #
124
+ # describe user('root') do
125
+ # it { should belong_to_group 'root' }
126
+ # it { should have_uid 0 }
127
+ # it { should have_home_directory '/root' }
128
+ # it { should have_login_shell '/bin/bash' }
129
+ # its('minimum_days_between_password_change') { should eq 0 }
130
+ # its('maximum_days_between_password_change') { should eq 99 }
131
+ # end
132
+ #
133
+ # ServerSpec tests that are not supported:
134
+ #
135
+ # describe user('root') do
136
+ # it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
137
+ # its(:encrypted_password) { should eq 1234 }
138
+ # end
139
+ class User < Inspec.resource(1)
140
+ include UserManagementSelector
43
141
  name 'user'
44
142
  desc 'Use the user InSpec audit resource to test user profiles, including the groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
45
143
  example "
@@ -49,33 +147,27 @@ module Inspec::Resources
49
147
  its('gid') { should eq 1234 }
50
148
  end
51
149
  "
52
- def initialize(user)
53
- @user = user
54
-
55
- # select package manager
56
- @user_provider = nil
57
- os = inspec.os
58
- if os.linux?
59
- @user_provider = LinuxUser.new(inspec)
60
- elsif os.windows?
61
- @user_provider = WindowsUser.new(inspec)
62
- elsif ['darwin'].include?(os[:family])
63
- @user_provider = DarwinUser.new(inspec)
64
- elsif ['freebsd'].include?(os[:family])
65
- @user_provider = FreeBSDUser.new(inspec)
66
- elsif ['aix'].include?(os[:family])
67
- @user_provider = AixUser.new(inspec)
68
- elsif os.solaris?
69
- @user_provider = SolarisUser.new(inspec)
70
- elsif ['hpux'].include?(os[:family])
71
- @user_provider = HpuxUser.new(inspec)
72
- else
73
- return skip_resource 'The `user` resource is not supported on your OS yet.'
74
- end
150
+ def initialize(username = nil)
151
+ @username = username
152
+ # select user provider
153
+ @user_provider = select_user_manager(inspec.os)
154
+ return skip_resource 'The `user` resource is not supported on your OS yet.' if @user_provider.nil?
75
155
  end
76
156
 
77
157
  def exists?
78
- !identity.nil? && !identity[:user].nil?
158
+ !identity.nil? && !identity[:username].nil?
159
+ end
160
+
161
+ def disabled?
162
+ identity[:disabled] == true unless identity.nil?
163
+ end
164
+
165
+ def enabled?
166
+ identity[:disabled] == false unless identity.nil?
167
+ end
168
+
169
+ def username
170
+ identity[:username] unless identity.nil?
79
171
  end
80
172
 
81
173
  def uid
@@ -86,9 +178,10 @@ module Inspec::Resources
86
178
  identity[:gid] unless identity.nil?
87
179
  end
88
180
 
89
- def group
90
- identity[:group] unless identity.nil?
181
+ def groupname
182
+ identity[:groupname] unless identity.nil?
91
183
  end
184
+ alias group groupname
92
185
 
93
186
  def groups
94
187
  identity[:groups] unless identity.nil?
@@ -156,27 +249,31 @@ module Inspec::Resources
156
249
  end
157
250
 
158
251
  def to_s
159
- "User #{@user}"
252
+ "User #{@username}"
160
253
  end
161
254
 
255
+ private
256
+
257
+ # returns the iden
162
258
  def identity
163
259
  return @id_cache if defined?(@id_cache)
164
- @id_cache = @user_provider.identity(@user) if !@user_provider.nil?
260
+ @id_cache = @user_provider.identity(@username) if !@user_provider.nil?
165
261
  end
166
262
 
167
- private
168
-
169
263
  def meta_info
170
264
  return @meta_cache if defined?(@meta_cache)
171
- @meta_cache = @user_provider.meta_info(@user) if !@user_provider.nil?
265
+ @meta_cache = @user_provider.meta_info(@username) if !@user_provider.nil?
172
266
  end
173
267
 
174
268
  def credentials
175
269
  return @cred_cache if defined?(@cred_cache)
176
- @cred_cache = @user_provider.credentials(@user) if !@user_provider.nil?
270
+ @cred_cache = @user_provider.credentials(@username) if !@user_provider.nil?
177
271
  end
178
272
  end
179
273
 
274
+ # This is an abstract class that every user provoider has to implement.
275
+ # A user provider implements a system abstracts and helps the InSpec resource
276
+ # hand-over system specific behavior to those providers
180
277
  class UserInfo
181
278
  include Converter
182
279
 
@@ -185,19 +282,76 @@ module Inspec::Resources
185
282
  @inspec = inspec
186
283
  end
187
284
 
285
+ # returns a hash with user-specific values:
286
+ # {
287
+ # uid: '',
288
+ # user: '',
289
+ # gid: '',
290
+ # group: '',
291
+ # groups: '',
292
+ # }
293
+ def identity(_username)
294
+ fail 'user provider must implement the `identity` method'
295
+ end
296
+
297
+ # returns optional information about a user, eg shell
298
+ def meta_info(_username)
299
+ nil
300
+ end
301
+
302
+ # returns a hash with meta-data about user credentials
303
+ # {
304
+ # mindays: 1,
305
+ # maxdays: 1,
306
+ # warndays: 1,
307
+ # }
308
+ # this method is optional and may not be implemented by each provider
188
309
  def credentials(_username)
310
+ nil
311
+ end
312
+
313
+ # returns an array with users
314
+ def list_users
315
+ fail 'user provider must implement the `list_users` method'
316
+ end
317
+
318
+ # retuns all aspects of the user as one hash
319
+ def user_details(username)
320
+ item = {}
321
+ id = identity(username)
322
+ item.merge!(id) unless id.nil?
323
+ meta = meta_info(username)
324
+ item.merge!(meta) unless meta.nil?
325
+ cred = credentials(username)
326
+ item.merge!(cred) unless cred.nil?
327
+ item
328
+ end
329
+
330
+ # returns the full information list for a user
331
+ def collect_user_details
332
+ list_users.map { |username|
333
+ user_details(username.chomp)
334
+ }
189
335
  end
190
336
  end
191
337
 
192
338
  # implements generic unix id handling
193
339
  class UnixUser < UserInfo
194
- attr_reader :inspec, :id_cmd
340
+ attr_reader :inspec, :id_cmd, :list_users_cmd
195
341
  def initialize(inspec)
196
342
  @inspec = inspec
197
343
  @id_cmd ||= 'id'
344
+ @list_users_cmd ||= 'cut -d: -f1 /etc/passwd | grep -v "^#"'
198
345
  super
199
346
  end
200
347
 
348
+ # returns a list of all local users on a system
349
+ def list_users
350
+ cmd = inspec.command(list_users_cmd)
351
+ return [] if cmd.exit_status != 0
352
+ cmd.stdout.chomp.lines
353
+ end
354
+
201
355
  # parse one id entry like '0(wheel)''
202
356
  def parse_value(line)
203
357
  SimpleConfig.new(
@@ -224,9 +378,9 @@ module Inspec::Resources
224
378
 
225
379
  {
226
380
  uid: convert_to_i(parse_value(params['uid']).keys[0]),
227
- user: parse_value(params['uid']).values[0],
381
+ username: parse_value(params['uid']).values[0],
228
382
  gid: convert_to_i(parse_value(params['gid']).keys[0]),
229
- group: parse_value(params['gid']).values[0],
383
+ groupname: parse_value(params['gid']).values[0],
230
384
  groups: parse_value(params['groups']).values,
231
385
  }
232
386
  end
@@ -283,10 +437,6 @@ module Inspec::Resources
283
437
  @id_cmd ||= 'id -a'
284
438
  super
285
439
  end
286
-
287
- def credentials(_username)
288
- nil
289
- end
290
440
  end
291
441
 
292
442
  class AixUser < UnixUser
@@ -350,6 +500,11 @@ module Inspec::Resources
350
500
  # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
351
501
  # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
352
502
  class DarwinUser < UnixUser
503
+ def initialize(inspec)
504
+ @list_users_cmd ||= 'dscl . list /Users'
505
+ super
506
+ end
507
+
353
508
  def meta_info(username)
354
509
  cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
355
510
  return nil if cmd.exit_status != 0
@@ -430,7 +585,7 @@ module Inspec::Resources
430
585
  # get related groups
431
586
  $groups = $user.GetRelated('Win32_Group') | Select-Object -Property Caption, Domain, Name, LocalAccount, SID, SIDType, Status
432
587
  # filter user information
433
- $user = $user | Select-Object -Property Caption, Description, Domain, Name, LocalAccount, Lockout, PasswordChangeable, PasswordExpires, PasswordRequired, SID, SIDType, Status
588
+ $user = $user | Select-Object -Property Caption, Description, Domain, Name, LocalAccount, Lockout, PasswordChangeable, PasswordExpires, PasswordRequired, SID, SIDType, Status, Disabled
434
589
  # build response object
435
590
  New-Object -Type PSObject | `
436
591
  Add-Member -MemberType NoteProperty -Name User -Value ($user) -PassThru | `
@@ -453,13 +608,13 @@ module Inspec::Resources
453
608
  # if groups is no array, generate one
454
609
  group_hashes = [group_hashes] unless group_hashes.is_a?(Array)
455
610
  group_names = group_hashes.map { |grp| grp['Caption'] }
456
-
457
611
  {
458
612
  uid: user_hash['SID'],
459
- user: user_hash['Caption'],
613
+ username: user_hash['Caption'],
460
614
  gid: nil,
461
615
  group: nil,
462
616
  groups: group_names,
617
+ disabled: user_hash['Disabled'],
463
618
  }
464
619
  end
465
620
 
@@ -470,5 +625,11 @@ module Inspec::Resources
470
625
  shell: nil,
471
626
  }
472
627
  end
628
+
629
+ def list_users
630
+ script = 'Get-WmiObject Win32_UserAccount | Select-Object -ExpandProperty Caption'
631
+ cmd = inspec.powershell(script)
632
+ cmd.stdout.chomp.lines
633
+ end
473
634
  end
474
635
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.33.2
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-07 00:00:00.000000000 Z
11
+ date: 2016-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train
@@ -261,6 +261,9 @@ files:
261
261
  - examples/kitchen-puppet/manifests/site.pp
262
262
  - examples/kitchen-puppet/metadata.json
263
263
  - examples/kitchen-puppet/test/integration/default/web_spec.rb
264
+ - examples/meta-profile/README.md
265
+ - examples/meta-profile/controls/example.rb
266
+ - examples/meta-profile/inspec.yml
264
267
  - examples/profile-attribute.yml
265
268
  - examples/profile-attribute/README.md
266
269
  - examples/profile-attribute/controls/example.rb
@@ -297,11 +300,10 @@ files:
297
300
  - lib/bundles/inspec-supermarket/api.rb
298
301
  - lib/bundles/inspec-supermarket/cli.rb
299
302
  - lib/bundles/inspec-supermarket/target.rb
303
+ - lib/fetchers/git.rb
300
304
  - lib/fetchers/local.rb
301
305
  - lib/fetchers/mock.rb
302
- - lib/fetchers/tar.rb
303
306
  - lib/fetchers/url.rb
304
- - lib/fetchers/zip.rb
305
307
  - lib/inspec.rb
306
308
  - lib/inspec/archive/tar.rb
307
309
  - lib/inspec/archive/zip.rb
@@ -323,6 +325,7 @@ files:
323
325
  - lib/inspec/errors.rb
324
326
  - lib/inspec/expect.rb
325
327
  - lib/inspec/fetcher.rb
328
+ - lib/inspec/file_provider.rb
326
329
  - lib/inspec/library_eval_context.rb
327
330
  - lib/inspec/log.rb
328
331
  - lib/inspec/metadata.rb
@@ -412,7 +415,8 @@ files:
412
415
  - lib/resources/shadow.rb
413
416
  - lib/resources/ssh_conf.rb
414
417
  - lib/resources/ssl.rb
415
- - lib/resources/user.rb
418
+ - lib/resources/sys_info.rb
419
+ - lib/resources/users.rb
416
420
  - lib/resources/vbscript.rb
417
421
  - lib/resources/windows_feature.rb
418
422
  - lib/resources/wmi.rb
data/lib/fetchers/tar.rb DELETED
@@ -1,53 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'rubygems/package'
6
- require 'zlib'
7
-
8
- module Fetchers
9
- class Tar < Inspec.fetcher(1)
10
- name 'tar'
11
- priority 100
12
-
13
- attr_reader :files
14
-
15
- def self.resolve(target)
16
- unless target.is_a?(String) && File.file?(target) && target.end_with?('.tar.gz', '.tgz')
17
- return nil
18
- end
19
- new(target)
20
- end
21
-
22
- def archive_path
23
- target
24
- end
25
-
26
- def initialize(target)
27
- @target = target
28
- @contents = {}
29
- @files = []
30
- Gem::Package::TarReader.new(Zlib::GzipReader.open(@target)) do |tar|
31
- @files = tar.map(&:full_name)
32
- end
33
- end
34
-
35
- def read(file)
36
- @contents[file] ||= read_from_tar(file)
37
- end
38
-
39
- def read_from_tar(file)
40
- return nil unless @files.include?(file)
41
- res = nil
42
- # NB `TarReader` includes `Enumerable` beginning with Ruby 2.x
43
- Gem::Package::TarReader.new(Zlib::GzipReader.open(@target)) do |tar|
44
- tar.each do |entry|
45
- next unless entry.file? && file == entry.full_name
46
- res = entry.read
47
- break
48
- end
49
- end
50
- res
51
- end
52
- end
53
- end
data/lib/fetchers/zip.rb DELETED
@@ -1,49 +0,0 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'zip'
6
-
7
- module Fetchers
8
- class Zip < Inspec.fetcher(1)
9
- name 'zip'
10
- priority 100
11
-
12
- attr_reader :files
13
-
14
- def self.resolve(target)
15
- unless target.is_a?(String) && File.file?(target) && target.end_with?('.zip')
16
- return nil
17
- end
18
- new(target)
19
- end
20
-
21
- def initialize(target)
22
- @target = target
23
- @contents = {}
24
- @files = []
25
- ::Zip::InputStream.open(@target) do |io|
26
- while (entry = io.get_next_entry)
27
- @files.push(entry.name.sub(%r{/+$}, ''))
28
- end
29
- end
30
- end
31
-
32
- def read(file)
33
- @contents[file] ||= read_from_zip(file)
34
- end
35
-
36
- def read_from_zip(file)
37
- return nil unless @files.include?(file)
38
- res = nil
39
- ::Zip::InputStream.open(@target) do |io|
40
- while (entry = io.get_next_entry)
41
- next unless file == entry.name
42
- res = io.read
43
- break
44
- end
45
- end
46
- res
47
- end
48
- end
49
- end